(window.webpackJsonp=window.webpackJsonp||[]).push([[7],{363:function(t,s,a){"use strict";a.r(s);var n=a(42),e=Object(n.a)({},(function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[a("h1",{attrs:{id:"echo-开源社区系统"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#echo-开源社区系统"}},[t._v("#")]),t._v(" Echo — 开源社区系统")]),t._v(" "),a("hr"),t._v(" "),a("h2",{attrs:{id:"📚-从本项目你能学到什么"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#📚-从本项目你能学到什么"}},[t._v("#")]),t._v(" 📚 从本项目你能学到什么")]),t._v(" "),a("ul",[a("li",[t._v("学会主流的 Java Web 开发技术和框架")]),t._v(" "),a("li",[t._v("积累一个真实的 Web 项目开发经验")]),t._v(" "),a("li",[t._v("掌握本项目中涉及的常见面试题的答题策略")])]),t._v(" "),a("h2",{attrs:{id:"💻-核心技术栈"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#💻-核心技术栈"}},[t._v("#")]),t._v(" 💻 核心技术栈")]),t._v(" "),a("p",[t._v("后端:")]),t._v(" "),a("ul",[a("li",[t._v("Spring")]),t._v(" "),a("li",[t._v("Spring Boot 2.1.5 RELEASE")]),t._v(" "),a("li",[t._v("Spring MVC")]),t._v(" "),a("li",[t._v("ORM:MyBatis")]),t._v(" "),a("li",[t._v("数据库:MySQL 5.7")]),t._v(" "),a("li",[t._v("日志:SLF4J(日志接口) + Logback(日志实现)")]),t._v(" "),a("li",[t._v("分布式缓存:Redis")]),t._v(" "),a("li",[t._v("本地缓存:Caffeine")]),t._v(" "),a("li",[t._v("消息队列:Kafka 2.13-2.7.0")]),t._v(" "),a("li",[t._v("搜索引擎:Elasticsearch 6.4.3")]),t._v(" "),a("li",[t._v("安全:Spring Security")]),t._v(" "),a("li",[t._v("邮件:Spring Mail")]),t._v(" "),a("li",[t._v("分布式定时任务:Spring Quartz")])]),t._v(" "),a("p",[t._v("前端:")]),t._v(" "),a("ul",[a("li",[t._v("Thymeleaf")]),t._v(" "),a("li",[t._v("Bootstrap 4.x")]),t._v(" "),a("li",[t._v("Jquery")]),t._v(" "),a("li",[t._v("Ajax")])]),t._v(" "),a("h2",{attrs:{id:"🔨-开发环境"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#🔨-开发环境"}},[t._v("#")]),t._v(" 🔨 开发环境")]),t._v(" "),a("ul",[a("li",[t._v("操作系统:Windows 10")]),t._v(" "),a("li",[t._v("构建工具:Apache Maven")]),t._v(" "),a("li",[t._v("集成开发工具:Intellij IDEA")]),t._v(" "),a("li",[t._v("数据库:MySQL 5.7")]),t._v(" "),a("li",[t._v("应用服务器:Apache Tomcat")]),t._v(" "),a("li",[t._v("接口测试工具:Postman")]),t._v(" "),a("li",[t._v("压力测试工具:Apache JMeter")]),t._v(" "),a("li",[t._v("版本控制工具:Git")])]),t._v(" "),a("h2",{attrs:{id:"🎨-功能列表"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#🎨-功能列表"}},[t._v("#")]),t._v(" 🎨 功能列表")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210208222403.png",alt:""}})]),t._v(" "),a("ul",[a("li",[a("p",[t._v("[x] "),a("strong",[t._v("注册")])]),t._v(" "),a("ul",[a("li",[t._v("用户注册成功,将用户信息存入 MySQL,但此时该用户状态为未激活")]),t._v(" "),a("li",[t._v("向用户发送激活邮件,用户点击链接则激活账号(Spring Mail)")])])]),t._v(" "),a("li",[a("p",[t._v("[x] "),a("strong",[t._v("登录 | 登出")])]),t._v(" "),a("ul",[a("li",[a("p",[t._v("进入登录界面,动态生成验证码,并将验证码短暂存入 Redis(60 秒)")])]),t._v(" "),a("li",[a("p",[t._v("用户登录成功(验证用户名、密码、验证码),生成登录凭证且设置状态为有效,并将登录凭证存入 Redis")]),t._v(" "),a("p",[t._v("注意:登录凭证存在有效期,在所有的请求执行之前,都会检查凭证是否有效和是否过期,只要该用户的凭证有效并在有效期时间内,本次请求就会一直持有该用户信息(使用 ThreadLocal 持有用户信息)")])]),t._v(" "),a("li",[a("p",[t._v("勾选记住我,则延长登录凭证有效时间")])]),t._v(" "),a("li",[a("p",[t._v("用户登录成功,将用户信息短暂存入 Redis(1 小时)")])]),t._v(" "),a("li",[a("p",[t._v("用户登出,将凭证状态设为无效,并更新 Redis 中该用户的登录凭证信息")])])])]),t._v(" "),a("li",[a("p",[t._v("[x] "),a("strong",[t._v("账号设置")])]),t._v(" "),a("ul",[a("li",[t._v("修改头像\n"),a("ul",[a("li",[t._v("将用户选择的头像图片文件上传至七牛云服务器")])])]),t._v(" "),a("li",[t._v("修改密码")])])]),t._v(" "),a("li",[a("p",[t._v("[x] "),a("strong",[t._v("帖子模块")])]),t._v(" "),a("ul",[a("li",[t._v("发布帖子(过滤敏感词),将其存入 MySQL")]),t._v(" "),a("li",[t._v("分页显示所有的帖子\n"),a("ul",[a("li",[t._v("支持按照 “发帖时间” 显示")]),t._v(" "),a("li",[t._v("支持按照 “热度排行” 显示(Spring Quartz)")])])]),t._v(" "),a("li",[t._v("查看帖子详情")]),t._v(" "),a("li",[t._v("权限管理(Spring Security + Thymeleaf Security)\n"),a("ul",[a("li",[t._v("未登录用户无法发帖")]),t._v(" "),a("li",[t._v("“版主” 可以看到帖子的置顶和加精按钮并执行相应操作")]),t._v(" "),a("li",[t._v("“管理员” 可以看到帖子的删除按钮并执行相应操作")]),t._v(" "),a("li",[t._v("“普通用户” 无法看到帖子的置顶、加精、删除按钮,也无法执行相应操作")])])])])]),t._v(" "),a("li",[a("p",[t._v("[x] "),a("strong",[t._v("评论模块")])]),t._v(" "),a("ul",[a("li",[t._v("发布对帖子的评论(过滤敏感词),将其存入 MySQL")]),t._v(" "),a("li",[t._v("分页显示评论")]),t._v(" "),a("li",[t._v("发布对评论的回复(过滤敏感词)")]),t._v(" "),a("li",[t._v("权限管理(Spring Security)\n"),a("ul",[a("li",[t._v("未登录用户无法使用评论功能")])])])])]),t._v(" "),a("li",[a("p",[t._v("[x] "),a("strong",[t._v("私信模块")])]),t._v(" "),a("ul",[a("li",[t._v("发送私信(过滤敏感词)")]),t._v(" "),a("li",[t._v("私信列表\n"),a("ul",[a("li",[t._v("查询当前用户的会话列表")]),t._v(" "),a("li",[t._v("每个会话只显示一条最新的私信")]),t._v(" "),a("li",[t._v("支持分页显示")])])]),t._v(" "),a("li",[t._v("私信详情\n"),a("ul",[a("li",[t._v("查询某个会话所包含的所有私信")]),t._v(" "),a("li",[t._v("访问私信详情时,将显示的私信设为已读状态")]),t._v(" "),a("li",[t._v("支持分页显示")])])]),t._v(" "),a("li",[t._v("权限管理(Spring Security)\n"),a("ul",[a("li",[t._v("未登录用户无法使用私信功能")])])])])]),t._v(" "),a("li",[a("p",[t._v("[x] "),a("strong",[t._v("统一处理 404 / 500 异常")])]),t._v(" "),a("ul",[a("li",[t._v("普通请求异常")]),t._v(" "),a("li",[t._v("异步请求异常")])])]),t._v(" "),a("li",[a("p",[t._v("[x] "),a("strong",[t._v("统一记录日志")])])]),t._v(" "),a("li",[a("p",[t._v("[x] "),a("strong",[t._v("点赞模块")])]),t._v(" "),a("ul",[a("li",[a("p",[t._v("支持对帖子、评论/回复点赞")])]),t._v(" "),a("li",[a("p",[t._v("第 1 次点赞,第 2 次取消点赞")])]),t._v(" "),a("li",[a("p",[t._v("首页统计帖子的点赞数量")])]),t._v(" "),a("li",[a("p",[t._v("详情页统计帖子和评论/回复的点赞数量")])]),t._v(" "),a("li",[a("p",[t._v("详情页显示当前登录用户的点赞状态(赞过了则显示已赞)")])]),t._v(" "),a("li",[a("p",[t._v("统计我的获赞数量")])]),t._v(" "),a("li",[a("p",[t._v("权限管理(Spring Security)")]),t._v(" "),a("ul",[a("li",[t._v("未登录用户无法使用点赞相关功能")])])])])]),t._v(" "),a("li",[a("p",[t._v("[x] "),a("strong",[t._v("关注模块")])]),t._v(" "),a("ul",[a("li",[t._v("关注功能")]),t._v(" "),a("li",[t._v("取消关注功能")]),t._v(" "),a("li",[t._v("统计用户的关注数和粉丝数")]),t._v(" "),a("li",[t._v("我的关注列表(查询某个用户关注的人),支持分页")]),t._v(" "),a("li",[t._v("我的粉丝列表(查询某个用户的粉丝),支持分页")]),t._v(" "),a("li",[t._v("权限管理(Spring Security)\n"),a("ul",[a("li",[t._v("未登录用户无法使用关注相关功能")])])])])]),t._v(" "),a("li",[a("p",[t._v("[x] "),a("strong",[t._v("系统通知模块")])]),t._v(" "),a("ul",[a("li",[t._v("通知列表\n"),a("ul",[a("li",[t._v("显示评论、点赞、关注三种类型的通知")])])]),t._v(" "),a("li",[t._v("通知详情\n"),a("ul",[a("li",[t._v("分页显示某一类主题所包含的通知")]),t._v(" "),a("li",[t._v("进入某种类型的系统通知详情,则将该页的所有未读的系统通知状态设置为已读")])])]),t._v(" "),a("li",[t._v("未读数量\n"),a("ul",[a("li",[t._v("分别显示每种类型的系统通知的未读数量")]),t._v(" "),a("li",[t._v("显示所有系统通知的未读数量")])])]),t._v(" "),a("li",[t._v("导航栏显示所有消息的未读数量(未读私信 + 未读系统通知)")]),t._v(" "),a("li",[t._v("权限管理(Spring Security)\n"),a("ul",[a("li",[t._v("未登录用户无法使用系统通知功能")])])])])]),t._v(" "),a("li",[a("p",[t._v("[x] "),a("strong",[t._v("搜索模块")])]),t._v(" "),a("ul",[a("li",[t._v("发布事件\n"),a("ul",[a("li",[t._v("发布帖子时,通过消息队列将帖子异步地提交到 Elasticsearch 服务器")]),t._v(" "),a("li",[t._v("为帖子增加评论时,通过消息队列将帖子异步地提交到 Elasticsearch 服务器")])])]),t._v(" "),a("li",[t._v("搜索服务\n"),a("ul",[a("li",[t._v("从 Elasticsearch 服务器搜索帖子")]),t._v(" "),a("li",[t._v("从 Elasticsearch 服务器删除帖子(当帖子从数据库中被删除时)")])])]),t._v(" "),a("li",[t._v("显示搜索结果")])])]),t._v(" "),a("li",[a("p",[t._v("[x] "),a("strong",[t._v("网站数据统计")]),t._v("(管理员专属)")]),t._v(" "),a("ul",[a("li",[t._v("独立访客 UV\n"),a("ul",[a("li",[t._v("存入 Redis 的 HyperLogLog")]),t._v(" "),a("li",[t._v("支持单日查询和区间日期查询")])])]),t._v(" "),a("li",[t._v("日活跃用户 DAU\n"),a("ul",[a("li",[t._v("存入 Redis 的 Bitmap")]),t._v(" "),a("li",[t._v("支持单日查询和区间日期查询")])])]),t._v(" "),a("li",[t._v("权限管理(Spring Security)\n"),a("ul",[a("li",[t._v("只有管理员可以查看网站数据统计")])])])])]),t._v(" "),a("li",[a("p",[t._v("[x] 优化网站性能")]),t._v(" "),a("ul",[a("li",[t._v("使用本地缓存 Caffeine 缓存热帖列表以及所有用户帖子的总数")])])])]),t._v(" "),a("h2",{attrs:{id:"🔐-待实现及优化"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#🔐-待实现及优化"}},[t._v("#")]),t._v(" 🔐 待实现及优化")]),t._v(" "),a("p",[t._v("以下是我个人发现的本项目存在的问题,但是暂时没有头绪无法解决,集思广益,欢迎各位小伙伴提 PR 解决:")]),t._v(" "),a("ul",[a("li",[t._v("[ ] 注册模块无法正常跳转到操作提示界面(本地运行没有问题)")]),t._v(" "),a("li",[t._v("[ ] 评论功能的前端显示部分存在 Bug")]),t._v(" "),a("li",[t._v("[ ] 查询我的评论(未完善)")])]),t._v(" "),a("p",[t._v("以下是我觉得本项目还可以添加的功能,同样欢迎各位小伙伴提 issue 指出还可以增加哪些功能,或者直接提 PR 实现该功能:")]),t._v(" "),a("ul",[a("li",[t._v("[ ] 忘记密码(发送邮件找回密码)")]),t._v(" "),a("li",[t._v("[ ] 查询我的点赞")]),t._v(" "),a("li",[t._v("[ ] 管理员对帖子的二次点击取消置顶功能")]),t._v(" "),a("li",[t._v("[ ] 管理员对已删除帖子的恢复功能(本项目中的删除帖子并未将其从数据库中删除,只是将其状态设置为了拉黑)")])]),t._v(" "),a("h2",{attrs:{id:"🎀-界面展示"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#🎀-界面展示"}},[t._v("#")]),t._v(" 🎀 界面展示")]),t._v(" "),a("h2",{attrs:{id:"🌱-本地运行"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#🌱-本地运行"}},[t._v("#")]),t._v(" 🌱 本地运行")]),t._v(" "),a("p",[t._v("各位如果需要将项目部署在本地进行测试,以下环境请提前备好:")]),t._v(" "),a("ul",[a("li",[t._v("Java 8")]),t._v(" "),a("li",[t._v("MySQL 5.7")]),t._v(" "),a("li",[t._v("Redis")]),t._v(" "),a("li",[t._v("Kafka 2.13-2.7.0")]),t._v(" "),a("li",[t._v("Elasticsearch 6.4.3")])]),t._v(" "),a("p",[t._v("然后"),a("strong",[t._v("修改配置文件中的信息为你自己的本地环境,直接运行是运行不了的")]),t._v(",而且相关私密信息我全部用 xxxxxxx 代替了。")]),t._v(" "),a("p",[t._v("本地运行需要修改的配置文件信息如下:")]),t._v(" "),a("p",[t._v("1)"),a("code",[t._v("application-develop.properties")]),t._v(":")]),t._v(" "),a("ul",[a("li",[t._v("MySQL")]),t._v(" "),a("li",[t._v("Spring Mail(邮箱需要开启 SMTP 服务)")]),t._v(" "),a("li",[t._v("Kafka:consumer.group-id(该字段见 Kafka 安装包中的 consumer.proerties,可自行修改, 修改完毕后需要重启 Kafka)")]),t._v(" "),a("li",[t._v("Elasticsearch:cluster-name(该字段见 Elasticsearch 安装包中的 elasticsearch.yml,可自行修改)")]),t._v(" "),a("li",[t._v("七牛云(需要新建一个七牛云的对象存储空间,用来存放上传的头像图片)")])]),t._v(" "),a("p",[t._v("2)"),a("code",[t._v("logback-spring-develop.xml")]),t._v(":")]),t._v(" "),a("ul",[a("li",[t._v("LOG_PATH:日志存放的位置")])]),t._v(" "),a("p",[t._v("每次运行需要打开:")]),t._v(" "),a("ul",[a("li",[t._v("MySQL")]),t._v(" "),a("li",[t._v("Redis")]),t._v(" "),a("li",[t._v("Elasticsearch")]),t._v(" "),a("li",[t._v("Kafka")])]),t._v(" "),a("h2",{attrs:{id:"📜-数据库设计"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#📜-数据库设计"}},[t._v("#")]),t._v(" 📜 数据库设计")]),t._v(" "),a("p",[t._v("用户 "),a("code",[t._v("user")]),t._v(":")]),t._v(" "),a("div",{staticClass:"language-sql extra-class"},[a("pre",{pre:!0,attrs:{class:"language-sql"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DROP")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("TABLE")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("IF")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("EXISTS")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("user")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("SET")]),t._v(" character_set_client "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" utf8mb4 "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("CREATE")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("TABLE")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("user")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("int")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("11")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("NOT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("AUTO_INCREMENT")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("username"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("varchar")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("50")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("password"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("varchar")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("50")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("salt"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("varchar")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("50")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("email"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("varchar")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("100")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("type")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("int")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("11")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("COMMENT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'0-普通用户; 1-超级管理员; 2-版主;'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("status")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("int")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("11")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("COMMENT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'0-未激活; 1-已激活;'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("activation_code"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("varchar")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("100")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("header_url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("varchar")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("200")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("create_time"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("timestamp")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("PRIMARY")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("KEY")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("KEY")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("index_username"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("username"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("20")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("KEY")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("index_email"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("email"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("20")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("ENGINE")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("InnoDB")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("AUTO_INCREMENT")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("101")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("CHARSET")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("utf8"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])])]),a("p",[t._v("讨论帖 "),a("code",[t._v("discuss_post")]),t._v(":")]),t._v(" "),a("div",{staticClass:"language-sql extra-class"},[a("pre",{pre:!0,attrs:{class:"language-sql"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DROP")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("TABLE")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("IF")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("EXISTS")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("discuss_post"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("SET")]),t._v(" character_set_client "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" utf8mb4 "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("CREATE")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("TABLE")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("discuss_post"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("int")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("11")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("NOT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("AUTO_INCREMENT")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("user_id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("int")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("11")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("title"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("varchar")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("100")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("content"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("text")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("type")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("int")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("11")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("COMMENT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'0-普通; 1-置顶;'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("status")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("int")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("11")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("COMMENT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'0-正常; 1-精华; 2-拉黑;'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("create_time"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("timestamp")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("comment_count"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("int")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("11")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("score"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("double")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("PRIMARY")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("KEY")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("KEY")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("index_user_id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("user_id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("ENGINE")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("InnoDB")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("CHARSET")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("utf8"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])])]),a("p",[t._v("评论(回复)"),a("code",[t._v("comment")]),t._v(":")]),t._v(" "),a("div",{staticClass:"language-sql extra-class"},[a("pre",{pre:!0,attrs:{class:"language-sql"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("CREATE")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("TABLE")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("comment")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("int")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("11")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("NOT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("AUTO_INCREMENT")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("user_id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("int")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("11")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("entity_type"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("int")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("11")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("COMMENT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'评论目标的类别:1 帖子;2 评论 '")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("entity_id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("int")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("11")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("COMMENT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'评论目标的 id'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("target_id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("int")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("11")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("COMMENT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'指明对谁进行评论'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("content"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("text")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("status")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("int")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("11")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("COMMENT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'状态:0 正常;1 禁用'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("create_time"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("timestamp")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("PRIMARY")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("KEY")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("KEY")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("index_user_id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("user_id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("KEY")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("index_entity_id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("entity_id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("ENGINE")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("InnoDB")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("AUTO_INCREMENT")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("247")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("CHARSET")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("utf8"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])])]),a("p",[t._v("私信 "),a("code",[t._v("message")]),t._v(":")]),t._v(" "),a("div",{staticClass:"language-sql extra-class"},[a("pre",{pre:!0,attrs:{class:"language-sql"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DROP")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("TABLE")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("IF")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("EXISTS")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("message"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("SET")]),t._v(" character_set_client "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" utf8mb4 "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("CREATE")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("TABLE")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("message"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("int")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("11")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("NOT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("AUTO_INCREMENT")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("from_id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("int")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("11")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("to_id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("int")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("11")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("conversation_id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("varchar")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("45")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("NOT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("content"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("text")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("status")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("int")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("11")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("COMMENT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'0-未读;1-已读;2-删除;'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("create_time"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("timestamp")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("NULL")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("PRIMARY")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("KEY")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("KEY")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("index_from_id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("from_id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("KEY")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("index_to_id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("to_id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("KEY")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("index_conversation_id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),t._v("conversation_id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("ENGINE")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("InnoDB")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("DEFAULT")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("CHARSET")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("utf8"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])])]),a("h2",{attrs:{id:"🎯-功能逻辑图"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#🎯-功能逻辑图"}},[t._v("#")]),t._v(" 🎯 功能逻辑图")]),t._v(" "),a("p",[t._v("画了一些不是那么严谨的图帮助各位小伙伴理清思绪。")]),t._v(" "),a("blockquote",[a("p",[t._v("单向绿色箭头:")]),t._v(" "),a("ul",[a("li",[t._v("前端模板 -> Controller:表示这个前端模板中有一个超链接是由这个 Controller 处理的")]),t._v(" "),a("li",[t._v("Controller -> 前端模板:表示这个 Controller 会像该前端模板传递数据或者跳转")])]),t._v(" "),a("p",[t._v("双向绿色箭头:表示 Controller 和前端模板之间进行参数的相互传递或使用")]),t._v(" "),a("p",[t._v("单向蓝色箭头: A -> B,表示 A 方法调用了 B 方法")]),t._v(" "),a("p",[t._v("单向红色箭头:数据库或缓存操作")])]),t._v(" "),a("h3",{attrs:{id:"注册"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#注册"}},[t._v("#")]),t._v(" 注册")]),t._v(" "),a("ul",[a("li",[t._v("用户注册成功,将用户信息存入 MySQL,但此时该用户状态为未激活")]),t._v(" "),a("li",[t._v("向用户发送激活邮件,用户点击链接则激活账号(Spring Mail)")])]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210204222249.png",alt:""}})]),t._v(" "),a("h3",{attrs:{id:"登录-登出"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#登录-登出"}},[t._v("#")]),t._v(" 登录 | 登出")]),t._v(" "),a("ul",[a("li",[a("p",[t._v("进入登录界面,动态生成验证码,并将验证码短暂存入 Redis(60 秒)")])]),t._v(" "),a("li",[a("p",[t._v("用户登录成功(验证用户名、密码、验证码),生成登录凭证且设置状态为有效,并将登录凭证存入 Redis")]),t._v(" "),a("p",[t._v("注意:登录凭证存在有效期,在所有的请求执行之前,都会检查凭证是否有效和是否过期,只要该用户的凭证有效并在有效期时间内,本次请求就会一直持有该用户信息(使用 ThreadLocal 持有用户信息)")])]),t._v(" "),a("li",[a("p",[t._v("勾选记住我,则延长登录凭证有效时间")])]),t._v(" "),a("li",[a("p",[t._v("用户登录成功,将用户信息短暂存入 Redis(1 小时)")])]),t._v(" "),a("li",[a("p",[t._v("用户登出,将凭证状态设为无效,并更新 Redis 中该用户的登录凭证信息")])])]),t._v(" "),a("p",[t._v("下图是登录模块的功能逻辑图,并没有使用 Spring Security 提供的认证逻辑(我觉得这个模块是最复杂的,这张图其实很多细节还没有画全)")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210204233233.png",alt:""}})]),t._v(" "),a("h3",{attrs:{id:"分页显示所有的帖子"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#分页显示所有的帖子"}},[t._v("#")]),t._v(" 分页显示所有的帖子")]),t._v(" "),a("ul",[a("li",[t._v("支持按照 “发帖时间” 显示")]),t._v(" "),a("li",[t._v("支持按照 “热度排行” 显示(Spring Quartz)")]),t._v(" "),a("li",[t._v("将热帖列表和所有帖子的总数存入本地缓存 Caffeine(利用分布式定时任务 Spring Quartz 每隔一段时间就刷新计算帖子的热度/分数 — 见下文,而 Caffeine 里的数据更新不用我们操心,它天生就会自动的更新它拥有的数据,给它一个初始化方法就完事儿)")])]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210204222822.png",alt:""}})]),t._v(" "),a("h3",{attrs:{id:"账号设置"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#账号设置"}},[t._v("#")]),t._v(" 账号设置")]),t._v(" "),a("ul",[a("li",[t._v("修改头像(异步请求)\n"),a("ul",[a("li",[t._v("将用户选择的头像图片文件上传至七牛云服务器")])])]),t._v(" "),a("li",[t._v("修改密码")])]),t._v(" "),a("p",[t._v("此处只画出修改头像:")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210206121201.png",alt:""}})]),t._v(" "),a("h3",{attrs:{id:"发布帖子-异步请求"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#发布帖子-异步请求"}},[t._v("#")]),t._v(" 发布帖子(异步请求)")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210206122521.png",alt:""}})]),t._v(" "),a("h3",{attrs:{id:"显示评论及相关信息"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#显示评论及相关信息"}},[t._v("#")]),t._v(" 显示评论及相关信息")]),t._v(" "),a("blockquote",[a("p",[t._v("评论部分前端的名称显示有些缺陷,有兴趣的小伙伴欢迎提 PR 解决~")])]),t._v(" "),a("p",[t._v("关于评论模块需要注意的就是评论表的设计,把握其中字段的含义,才能透彻了解这个功能的逻辑。")]),t._v(" "),a("p",[t._v("评论 Comment 的目标类型(帖子,评论) entityType 和 entityId 以及对哪个用户进行评论/回复 targetId 是由前端传递给 DiscussPostController 的")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210207150925.png",alt:""}})]),t._v(" "),a("p",[t._v("一个帖子的详情页需要封装的信息大概如下:")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210207151328.png",alt:""}})]),t._v(" "),a("h3",{attrs:{id:"添加评论-事务管理"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#添加评论-事务管理"}},[t._v("#")]),t._v(" 添加评论(事务管理)")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210207122908.png",alt:""}})]),t._v(" "),a("h3",{attrs:{id:"私信列表和详情页"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#私信列表和详情页"}},[t._v("#")]),t._v(" 私信列表和详情页")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210207161130.png",alt:""}})]),t._v(" "),a("h3",{attrs:{id:"发送私信-异步请求"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#发送私信-异步请求"}},[t._v("#")]),t._v(" 发送私信(异步请求)")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210207161500.png",alt:""}})]),t._v(" "),a("h3",{attrs:{id:"点赞-异步请求"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#点赞-异步请求"}},[t._v("#")]),t._v(" 点赞(异步请求)")]),t._v(" "),a("p",[t._v("将点赞相关信息存入 Redis 的数据结构 set 中。其中,key 命名为 "),a("code",[t._v("like:entity:entityType:entityId")]),t._v(",value 即点赞用户的 id。比如 key = "),a("code",[t._v("like:entity:2:246")]),t._v(" value = "),a("code",[t._v("11")]),t._v(" 表示用户 11 对实体类型 2 即评论进行了点赞,该评论的 id 是 246")]),t._v(" "),a("p",[t._v("某个用户的获赞数量对应的存储在 Redis 中的 key 是 "),a("code",[t._v("like:user:userId")]),t._v(",value 就是这个用户的获赞数量")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210207165837.png",alt:""}})]),t._v(" "),a("h3",{attrs:{id:"我的获赞数量"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#我的获赞数量"}},[t._v("#")]),t._v(" 我的获赞数量")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210207170003.png",alt:""}})]),t._v(" "),a("h3",{attrs:{id:"关注-异步请求"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#关注-异步请求"}},[t._v("#")]),t._v(" 关注(异步请求)")]),t._v(" "),a("ul",[a("li",[t._v("若 A 关注了 B,则 A 是 B 的粉丝 Follower,B 是 A 的目标 Followee")]),t._v(" "),a("li",[t._v("关注的目标可以是用户、帖子、题目等,在实现时将这些目标抽象为实体(目前只做了关注用户)")])]),t._v(" "),a("p",[t._v("将某个用户关注的实体相关信息存储在 Redis 的数据结构 zset 中:key 是 "),a("code",[t._v("followee:userId:entityType")]),t._v(" ,对应的 value 是 "),a("code",[t._v("zset(entityId, now)")]),t._v(" ,以关注的时间进行排序。比如说 "),a("code",[t._v("followee:111:3")]),t._v(" 对应的value "),a("code",[t._v("(20, 2020-02-03-xxxx)")]),t._v(",表明用户 111 关注了实体类型为 3 即人(用户),该帖子的 id 是 20,关注该帖子的时间是 2020-02-03-xxxx")]),t._v(" "),a("p",[t._v("同样的,将某个实体拥有的粉丝相关信息也存储在 Redis 的数据结构 zset 中:key 是 "),a("code",[t._v("follower:entityType:entityId")]),t._v(",对应的 value 是 "),a("code",[t._v("zset(userId, now)")]),t._v(",以关注的时间进行排序")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210207174046.png",alt:""}})]),t._v(" "),a("h3",{attrs:{id:"关注列表"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#关注列表"}},[t._v("#")]),t._v(" 关注列表")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210207175621.png",alt:""}})]),t._v(" "),a("h3",{attrs:{id:"发送系统通知"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#发送系统通知"}},[t._v("#")]),t._v(" 发送系统通知")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210207182917.png",alt:""}})]),t._v(" "),a("h3",{attrs:{id:"显示系统通知"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#显示系统通知"}},[t._v("#")]),t._v(" 显示系统通知")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210208153059.png",alt:""}})]),t._v(" "),a("h3",{attrs:{id:"搜索"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#搜索"}},[t._v("#")]),t._v(" 搜索")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210208161936.png",alt:""}})]),t._v(" "),a("p",[t._v("类似的,置顶、加精也会触发发帖事件,就不再图里面画出来了。")]),t._v(" "),a("h3",{attrs:{id:"置顶加精删除-异步请求"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#置顶加精删除-异步请求"}},[t._v("#")]),t._v(" 置顶加精删除(异步请求)")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210208171729.png",alt:""}})]),t._v(" "),a("h3",{attrs:{id:"网站数据统计"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#网站数据统计"}},[t._v("#")]),t._v(" 网站数据统计")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210208170801.png",alt:""}})]),t._v(" "),a("h3",{attrs:{id:"帖子热度计算"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#帖子热度计算"}},[t._v("#")]),t._v(" 帖子热度计算")]),t._v(" "),a("p",[t._v("每次发生点赞(给帖子点赞)、评论(给帖子评论)、加精的时候,就将这些帖子信息存入缓存 Redis 中,然后通过分布式的定时任务 Spring Quartz,每隔一段时间就从缓存中取出这些帖子进行计算分数。")]),t._v(" "),a("p",[t._v("帖子分数/热度计算公式:分数(热度) = 权重 + 发帖距离天数")]),t._v(" "),a("div",{staticClass:"language-java extra-class"},[a("pre",{pre:!0,attrs:{class:"language-java"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 计算权重")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("double")]),t._v(" w "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("wonderful "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("?")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("75")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" commentCount "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("*")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("10")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" likeCount "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("*")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("2")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 分数 = 权重 + 发帖距离天数")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("double")]),t._v(" score "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Math")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log10")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Math")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("max")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("w"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("1")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("post"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("getCreateTime")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("getTime")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("-")]),t._v(" epoch"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("getTime")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("1000")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("*")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("3600")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("*")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("24")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])])]),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210208173636.png",alt:""}})]),t._v(" "),a("h2",{attrs:{id:"🌌-理想的部署架构"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#🌌-理想的部署架构"}},[t._v("#")]),t._v(" 🌌 理想的部署架构")]),t._v(" "),a("p",[t._v("我只部署了一台服务器,以下是理想的部署架构:")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210206215827.png",alt:""}})]),t._v(" "),a("h2",{attrs:{id:"📖-常见面试题"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#📖-常见面试题"}},[t._v("#")]),t._v(" 📖 常见面试题")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210208221333.png",alt:""}})]),t._v(" "),a("p",[t._v("有意愿拿着这个项目去面试(校招/社招)的小伙伴,可以扫描下方二维码关注公众号**『飞天小牛肉』**,获取我整理的关于本项目涉及的常见面试题及解析(尚在更新中)")]),t._v(" "),a("p",[a("img",{attrs:{src:"https://gitee.com/veal98/images/raw/master/img/20210206220907.png",alt:""}})])])}),[],!1,null,null,null);s.default=e.exports}}]);