? 專案簡介
Echo 是一套前後端不分離的開源社群系統,基於目前主流 Java Web 技術棧(SpringBoot + MyBatis + MySQL + Redis + Kafka + Elasticsearch + Spring Security + ...),並提供詳細的開發文件和配套教程。包含帖子、評論、私信、系統通知、點贊、關注、搜尋、使用者設定、資料統計等模組。
原始碼連結:已託管在 Github 和 Gitee:
-
Github(推薦):https://github.com/Veal98/Echo
線上體驗:專案已經部署到騰訊雲伺服器,各位小夥伴們可直接線上體驗:http://1.15.127.74/。已內建三種不同身份的使用者:
username | password | 特殊許可權 | |
---|---|---|---|
管理員 | admin | admin | 資料統計、刪除帖子 |
版主 | master | master | 置頂帖子、加精帖子 |
普通使用者 | user | user |
文件地址:文件通過 Docsify + Gitee Pages 生成,國內訪問速度較快,線上訪問地址:https://veal98.gitee.io/echo
? 核心技術棧
後端:
-
Spring
-
Spring Boot 2.1.5 RELEASE
-
Spring MVC
-
ORM:MyBatis
-
資料庫:MySQL 5.7
-
分散式快取:Redis
-
本地快取:Caffeine
-
訊息佇列:Kafka 2.13-2.7.0
-
搜尋引擎:Elasticsearch 6.4.3
-
安全:Spring Security
-
郵件任務:Spring Mail
-
分散式定時任務:Spring Quartz
-
日誌:SLF4J(日誌介面) + Logback(日誌實現)
前端:
-
Thymeleaf
-
Bootstrap 4.x
-
Jquery
-
Ajax
? 開發環境
-
作業系統:Windows 10
-
構建工具:Apache Maven
-
整合開發工具:Intellij IDEA
-
應用伺服器:Apache Tomcat
-
介面測試工具:Postman
-
壓力測試工具:Apache JMeter
-
版本控制工具:Git
-
Java 版本:8
? 介面展示
首頁:
登入頁:
帖子詳情頁:
個人主頁:
朋友私信頁:
私信詳情頁:
系統通知頁:
通知詳情頁:
賬號設定頁:
資料統計頁:
搜尋詳情頁:
? 功能列表
- 註冊
-
使用者註冊成功,將使用者資訊存入 MySQL,但此時該使用者狀態為未啟用
-
向使用者傳送啟用郵件,使用者點選連結則啟用賬號(Spring Mail)
-
- 登入 | 登出
-
進入登入介面,動態生成驗證碼,並將驗證碼短暫存入 Redis(60 秒)
-
使用者登入成功(驗證使用者名稱、密碼、驗證碼),生成登入憑證且設定狀態為有效,並將登入憑證存入 Redis 注意:登入憑證存在有效期,在所有的請求執行之前,都會檢查憑證是否有效和是否過期,只要該使用者的憑證有效並在有效期時間內,本次請求就會一直持有該使用者資訊(使用 ThreadLocal 持有使用者資訊)
-
勾選記住我,則延長登入憑證有效時間
-
使用者登入成功,將使用者資訊短暫存入 Redis(1 小時)
-
使用者登出,將憑證狀態設為無效,並更新 Redis 中該使用者的登入憑證資訊
-
- 賬號設定
-
修改頭像
-
將使用者選擇的頭像圖片檔案上傳至七牛雲伺服器
-
-
修改密碼
-
- 帖子模組
-
釋出帖子(過濾敏感詞),將其存入 MySQL
-
分頁顯示所有的帖子
-
支援按照 “發帖時間” 顯示
-
支援按照 “熱度排行” 顯示(Spring Quartz)
-
-
檢視帖子詳情
-
許可權管理(Spring Security + Thymeleaf Security)
-
未登入使用者無法發帖
-
“版主” 可以看到帖子的置頂和加精按鈕並執行相應操作
-
“管理員” 可以看到帖子的刪除按鈕並執行相應操作
-
“普通使用者” 無法看到帖子的置頂、加精、刪除按鈕,也無法執行相應操作
-
-
- 評論模組
-
釋出對帖子的評論(過濾敏感詞),將其存入 MySQL
-
分頁顯示評論
-
釋出對評論的回覆(過濾敏感詞)
-
許可權管理(Spring Security)
-
未登入使用者無法使用評論功能
-
-
- 私信模組
-
傳送私信(過濾敏感詞)
-
私信列表
-
查詢當前使用者的會話列表
-
每個會話只顯示一條最新的私信
-
支援分頁顯示
-
-
私信詳情
-
查詢某個會話所包含的所有私信
-
訪問私信詳情時,將顯示的私信設為已讀狀態
-
支援分頁顯示
-
-
許可權管理(Spring Security)
-
未登入使用者無法使用私信功能
-
-
- 統一處理 404 / 500 異常
-
普通請求異常
-
非同步請求異常
-
- 統一記錄日誌
- 點贊模組
-
支援對帖子、評論/回覆點贊
-
第 1 次點贊,第 2 次取消點贊
-
首頁統計帖子的點贊數量
-
詳情頁統計帖子和評論/回覆的點贊數量
-
詳情頁顯示當前登入使用者的點贊狀態(贊過了則顯示已贊)
-
統計我的獲贊數量
-
許可權管理(Spring Security)
-
未登入使用者無法使用點贊相關功能
-
-
- 關注模組
-
關注功能
-
取消關注功能
-
統計使用者的關注數和粉絲數
-
我的關注列表(查詢某個使用者關注的人),支援分頁
-
我的粉絲列表(查詢某個使用者的粉絲),支援分頁
-
許可權管理(Spring Security)
-
未登入使用者無法使用關注相關功能
-
-
- 系統通知模組
-
通知列表
-
顯示評論、點贊、關注三種型別的通知
-
-
通知詳情
-
分頁顯示某一類主題所包含的通知
-
進入某種型別的系統通知詳情,則將該頁的所有未讀的系統通知狀態設定為已讀
-
-
未讀數量
-
分別顯示每種型別的系統通知的未讀數量
-
顯示所有系統通知的未讀數量
-
-
導航欄顯示所有訊息的未讀數量(未讀私信 + 未讀系統通知)
-
許可權管理(Spring Security)
-
未登入使用者無法使用系統通知功能
-
-
- 搜尋模組
-
釋出事件
-
釋出帖子時,通過訊息佇列將帖子非同步地提交到 Elasticsearch 伺服器
-
為帖子增加評論時,通過訊息佇列將帖子非同步地提交到 Elasticsearch 伺服器
-
-
搜尋服務
-
從 Elasticsearch 伺服器搜尋帖子
-
從 Elasticsearch 伺服器刪除帖子(當帖子從資料庫中被刪除時)
-
-
顯示搜尋結果
-
- 網站資料統計(管理員專屬)
-
獨立訪客 UV
-
存入 Redis 的 HyperLogLog
-
支援單日查詢和區間日期查詢
-
-
日活躍使用者 DAU
-
存入 Redis 的 Bitmap
-
支援單日查詢和區間日期查詢
-
-
許可權管理(Spring Security)
-
只有管理員可以檢視網站資料統計
-
-
- 優化網站效能
-
使用本地快取 Caffeine 快取熱帖列表以及所有使用者帖子的總數
-
? 快速開始
各位如果需要將專案部署在本地進行測試,以下環境請提前備好:
-
Java 8
-
MySQL 5.7
-
Redis
-
Kafka 2.13-2.7.0
-
Elasticsearch 6.4.3
然後修改配置檔案中的資訊為你自己的本地環境,直接執行是執行不了的,而且相關私密資訊我全部用 xxxxxxx 代替了。
本地執行需要修改的配置檔案資訊如下:
1)application-develop.properties
:
-
MySQL
-
Spring Mail(郵箱需要開啟 SMTP 服務)
-
Kafka:consumer.group-id(該欄位見 Kafka 安裝包中的 consumer.proerties,可自行修改, 修改完畢後需要重啟 Kafka)
-
Elasticsearch:cluster-name(該欄位見 Elasticsearch 安裝包中的 elasticsearch.yml,可自行修改)
-
七牛雲(需要新建一個七牛雲的物件儲存空間,用來存放上傳的頭像圖片)
2)logback-spring-develop.xml
:
-
LOG_PATH:日誌存放的位置
每次執行需要開啟:
-
MySQL
-
Redis
-
Elasticsearch
-
Kafka
另外,還需要事件建好資料庫 greatecommunity,然後依次執行專案 sql 資料夾下的這幾個 sql 檔案建立資料庫表:
? 資料庫設計
使用者 user
:
DROP TABLE IF EXISTS `user`;
SET character_set_client = utf8mb4 ;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
`password` varchar(50) DEFAULT NULL,
`salt` varchar(50) DEFAULT NULL,
`email` varchar(100) DEFAULT NULL,
`type` int(11) DEFAULT NULL COMMENT '0-普通使用者; 1-超級管理員; 2-版主;',
`status` int(11) DEFAULT NULL COMMENT '0-未啟用; 1-已啟用;',
`activation_code` varchar(100) DEFAULT NULL,
`header_url` varchar(200) DEFAULT NULL,
`create_time` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_username` (`username`(20)),
KEY `index_email` (`email`(20))
) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8;
討論帖 discuss_post
:
DROP TABLE IF EXISTS `discuss_post`;
SET character_set_client = utf8mb4 ;
CREATE TABLE `discuss_post` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`title` varchar(100) DEFAULT NULL,
`content` text,
`type` int(11) DEFAULT NULL COMMENT '0-普通; 1-置頂;',
`status` int(11) DEFAULT NULL COMMENT '0-正常; 1-精華; 2-拉黑;',
`create_time` timestamp NULL DEFAULT NULL,
`comment_count` int(11) DEFAULT NULL,
`score` double DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
評論(回覆)comment
:
CREATE TABLE `comment` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`entity_type` int(11) DEFAULT NULL COMMENT '評論目標的類別:1 帖子;2 評論 ',
`entity_id` int(11) DEFAULT NULL COMMENT '評論目標的 id',
`target_id` int(11) DEFAULT NULL COMMENT '指明對誰進行評論',
`content` text,
`status` int(11) DEFAULT NULL COMMENT '狀態:0 正常;1 禁用',
`create_time` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_user_id` (`user_id`),
KEY `index_entity_id` (`entity_id`)
) ENGINE=InnoDB AUTO_INCREMENT=247 DEFAULT CHARSET=utf8;
私信 message
:
DROP TABLE IF EXISTS `message`;
SET character_set_client = utf8mb4 ;
CREATE TABLE `message` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`from_id` int(11) DEFAULT NULL,
`to_id` int(11) DEFAULT NULL,
`conversation_id` varchar(45) NOT NULL,
`content` text,
`status` int(11) DEFAULT NULL COMMENT '0-未讀;1-已讀;2-刪除;',
`create_time` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_from_id` (`from_id`),
KEY `index_to_id` (`to_id`),
KEY `index_conversation_id` (`conversation_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
? 部署架構
我每個都只部署了一臺,以下是理想的部署架構:
? 功能邏輯圖
畫了一些不是那麼嚴謹的圖幫助各位小夥伴理清思緒。
單向綠色箭頭:
前端模板 -> Controller:表示這個前端模板中有一個超連結是由這個 Controller 處理的
Controller -> 前端模板:表示這個 Controller 會像該前端模板傳遞資料或者跳轉
雙向綠色箭頭:表示 Controller 和前端模板之間進行引數的相互傳遞或使用
單向藍色箭頭: A -> B,表示 A 方法呼叫了 B 方法
單向紅色箭頭:資料庫或快取操作
註冊
-
使用者註冊成功,將使用者資訊存入 MySQL,但此時該使用者狀態為未啟用
-
向使用者傳送啟用郵件,使用者點選連結則啟用賬號(Spring Mail)
登入 | 登出
-
進入登入介面,動態生成驗證碼,並將驗證碼短暫存入 Redis(60 秒)
-
使用者登入成功(驗證使用者名稱、密碼、驗證碼),生成登入憑證且設定狀態為有效,並將登入憑證存入 Redis
注意:登入憑證存在有效期,在所有的請求執行之前,都會檢查憑證是否有效和是否過期,只要該使用者的憑證有效並在有效期時間內,本次請求就會一直持有該使用者資訊(使用 ThreadLocal 持有使用者資訊)
-
勾選記住我,則延長登入憑證有效時間
-
使用者登入成功,將使用者資訊短暫存入 Redis(1 小時)
-
使用者登出,將憑證狀態設為無效,並更新 Redis 中該使用者的登入憑證資訊
下圖是登入模組的功能邏輯圖,並沒有使用 Spring Security 提供的認證邏輯(我覺得這個模組是最複雜的,這張圖其實很多細節還沒有畫全)
分頁顯示所有的帖子
-
支援按照 “發帖時間” 顯示
-
支援按照 “熱度排行” 顯示(Spring Quartz)
-
將熱帖列表和所有帖子的總數存入本地快取 Caffeine(利用分散式定時任務 Spring Quartz 每隔一段時間就重新整理計算帖子的熱度/分數 — 見下文,而 Caffeine 裡的資料更新不用我們操心,它天生就會自動的更新它擁有的資料,給它一個初始化方法就完事兒)
賬號設定
-
修改頭像(非同步請求)
-
將使用者選擇的頭像圖片檔案上傳至七牛雲伺服器
-
-
修改密碼
此處只畫出修改頭像:
釋出帖子(非同步請求)
顯示評論及相關資訊
評論部分前端的名稱顯示有些缺陷,有興趣的小夥伴歡迎提 PR 解決~
關於評論模組需要注意的就是評論表的設計,把握其中欄位的含義,才能透徹瞭解這個功能的邏輯。
評論 Comment 的目標型別(帖子,評論) entityType 和 entityId 以及對哪個使用者進行評論/回覆 targetId 是由前端傳遞給 DiscussPostController 的
一個帖子的詳情頁需要封裝的資訊大概如下:
新增評論(事務管理)
私信列表和詳情頁
傳送私信(非同步請求)
點贊(非同步請求)
將點贊相關資訊存入 Redis 的資料結構 set 中。其中,key 命名為 like:entity:entityType:entityId
,value 即點贊使用者的 id。比如 key = like:entity:2:246
value = 11
表示使用者 11 對實體型別 2 即評論進行了點贊,該評論的 id 是 246
某個使用者的獲贊數量對應的儲存在 Redis 中的 key 是 like:user:userId
,value 就是這個使用者的獲贊數量
我的獲贊數量
關注(非同步請求)
-
若 A 關注了 B,則 A 是 B 的粉絲 Follower,B 是 A 的目標 Followee
-
關注的目標可以是使用者、帖子、題目等,在實現時將這些目標抽象為實體(目前只做了關注使用者)
將某個使用者關注的實體相關資訊儲存在 Redis 的資料結構 zset 中:key 是 followee:userId:entityType
,對應的 value 是 zset(entityId, now)
,以關注的時間進行排序。比如說 followee:111:3
對應的value (20, 2020-02-03-xxxx)
,表明使用者 111 關注了實體型別為 3 即人(使用者),該帖子的 id 是 20,關注該帖子的時間是 2020-02-03-xxxx
同樣的,將某個實體擁有的粉絲相關資訊也儲存在 Redis 的資料結構 zset 中:key 是 follower:entityType:entityId
,對應的 value 是 zset(userId, now)
,以關注的時間進行排序
關注列表
傳送系統通知
顯示系統通知
搜尋
類似的,置頂、加精也會觸發發帖事件,就不再圖裡面畫出來了。
置頂加精刪除(非同步請求)
網站資料統計
帖子熱度計算
每次發生點贊(給帖子點贊)、評論(給帖子評論)、加精的時候,就將這些帖子資訊存入快取 Redis 中,然後通過分散式的定時任務 Spring Quartz,每隔一段時間就從快取中取出這些帖子進行計算分數。
帖子分數/熱度計算公式:分數(熱度) = 權重 + 發帖距離天數
// 計算權重
double w = (wonderful ? 75 : 0) + commentCount * 10 + likeCount * 2;
// 分數 = 權重 + 發帖距離天數
double score = Math.log10(Math.max(w, 1))
+ (post.getCreateTime().getTime() - epoch.getTime()) / (1000 * 3600 * 24);
? 配套教程
想要自己從零開始實現這個專案或者深入理解的小夥伴,可以關注公眾號『飛天小牛肉』,回覆 Echo 獲取配套教程, 不僅會詳細解釋本專案涉及的各大技術點,還會彙總相關的常見面試題,目前尚在更新中
並推薦我的開源教程類專案 『 CS-Wiki 』,Gitee 推薦專案,目前已 1.0k+ star: 致力打造完善的 Java 後端知識體系,不僅僅幫助各位小夥伴快速且系統的準備面試(秋招/社招),更指引學習的方向