你好呀,我是小鄒。
個人博客系統日漸完善,現在的文章評論以及留言數量逐漸增多,所以今天重構了管理后臺的評論列表(全量查詢 -> 分頁條件搜索)。
示例圖
網頁端
手機端
一、系統架構設計與技術選型
系統采用前后端分離架構,但后端保留模板渲染能力(Thymeleaf),兼顧管理后臺的快速開發與前后端協作的靈活性。核心選型如下:
層次 | 技術/框架 | 選擇理由 |
---|---|---|
后端 | Spring Boot 2.7.x | 簡化配置、自動裝配、內置Tomcat,快速構建生產級應用 |
ORM | MyBatis-Plus 3.5.x | 基于MyBatis的增強工具,提供CRUD簡化、分頁插件、代碼生成器,提升開發效率 |
緩存 | Redis | 高頻數據緩存(如評論列表)、分布式會話管理,降低數據庫壓力 |
前端模板 | Thymeleaf | 支持服務端渲染,保留HTML原生結構,便于SEO和瀏覽器兼容 |
UI框架 | Bootstrap 5 + Font Awesome | 響應式布局、移動優先設計,圖標庫豐富交互體驗 |
數據校驗 | Hibernate Validator | 注解驅動的參數校驗,統一處理請求參數合法性 |
2. 分層架構設計
- 客戶端層:支持PC(Chrome/Firefox)與移動端(iOS/Android),通過響應式設計適配不同屏幕。
- 網關層:Nginx負載均衡,靜態資源緩存(
expires 30d;
),反向代理后端服務。 - 應用層:
- Controller:處理HTTP請求,參數校驗,調用Service,返回視圖或JSON。
- Service:業務邏輯封裝(如評論狀態變更、批量操作),事務管理(
@Transactional
)。 - Mapper:MyBatis-Plus接口,通過XML/注解定義SQL,與數據庫交互。
- 數據層:MySQL存儲評論數據(
comments
表),Redis緩存高頻訪問的評論列表。
二、后端核心模塊深度解析
1. 分頁查詢與動態條件構造
(1)分頁參數處理
@GetMapping
public String viewComments(Model model, @RequestParam(defaultValue = "1") Integer pageNum,@RequestParam(defaultValue = "10") Integer pageSize,@RequestParam(required = false) Integer status,@RequestParam(required = false) String keyword) {// 構造分頁對象(MyBatis-Plus內置Page)Page<Comments> page = new Page<>(pageNum, pageSize);// 動態查詢條件構造(LambdaQueryWrapper類型安全)LambdaQueryWrapper<Comments> queryWrapper = new LambdaQueryWrapper<>();// 狀態篩選:status為null時不生效,>=0避免非法參數if (status != null && status >= 0) {queryWrapper.eq(Comments::getIsVisible, status); // is_visible字段存儲審核狀態(0未審核/1已發布)}// 關鍵詞搜索:昵稱/內容/IP三選一匹配(使用AND連接OR條件)if (StringUtils.hasText(keyword)) { // StringUtils來自Spring Util,判斷非空且非空白queryWrapper.and(wrapper -> wrapper.like(Comments::getNickname, keyword) // 昵稱模糊匹配.or() // 或.like(Comments::getContent, keyword) // 內容模糊匹配.or() // 或.like(Comments::getIp, keyword) // IP模糊匹配);}// 排序規則:優先展示待審核評論(is_visible=0在前),按創建時間倒序queryWrapper.orderByAsc(Comments::getIsVisible) // 升序:0在1前.orderByDesc(Comments::getCreateTime); // 降序:最新評論在前// 執行分頁查詢(MyBatis-Plus自動轉換為LIMIT/OFFSET)IPage<Comments> commentPage = commentService.page(page, queryWrapper);// 傳遞數據到前端模板model.addAttribute("commentPage", commentPage);model.addAttribute("currentPage", pageNum);model.addAttribute("pageSize", pageSize);model.addAttribute("totalPages", commentPage.getPages()); // 總頁數model.addAttribute("status", status); // 回顯篩選狀態model.addAttribute("keyword", keyword != null ? keyword : ""); // 回顯關鍵詞(避免空值)return "admin/admin_comments"; // 返回Thymeleaf模板路徑
}
(2)分頁插件原理
MyBatis-Plus的分頁功能通過PaginationInnerInterceptor
實現,核心流程如下:
- 參數解析:從請求中獲取
pageNum
和pageSize
,生成Page
對象。 - SQL改寫:攔截原始SQL,添加
LIMIT offset, size
(MySQL)或OFFSET offset ROWS FETCH NEXT size ROWS ONLY
(Oracle)。 - 總記錄數查詢:執行
SELECT COUNT(*) FROM ...
獲取總數據量,計算總頁數。 - 結果封裝:將查詢結果封裝為
IPage
對象,包含當前頁數據、總條數、總頁數等信息。
優化點:在application.yml
中配置分頁插件,限制最大分頁大小防止內存溢出:
mybatis-plus:configuration:map-underscore-to-camel-case: true # 駝峰命名自動映射global-config:db-config:logic-delete-field: is_deleted # 邏輯刪除字段(示例)logic-delete-value: 1 # 刪除值logic-not-delete-value: 0 # 未刪除值plugins:pagination:interceptors: - com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptortype: MYSQL # 數據庫類型pageSizeZero: false # pageSize=0返回全部maxPageSize: 100 # 最大單頁數據量
2. 批量操作與事務管理
(1)批量操作接口實現
@PostMapping("/batchOperation")
public ResponseEntity<Void> batchOperation(@RequestParam("ids[]") List<Integer> ids, // 前端傳遞的ID數組@RequestParam("operation") String operation) { // 操作類型(delete/visible/invisible)try {if (ids == null || ids.isEmpty()) {return ResponseEntity.badRequest().build(); // 參數校驗:ID不能為空}switch (operation.toLowerCase()) {case "delete":// 批量刪除(調用MyBatis-Plus的removeBatchByIds)commentService.removeBatchByIds(ids);break;case "visible":// 批量通過審核(更新is_visible=1)updateBatchStatus(ids, CommentStatus.VISIBLE.getStatus());break;case "invisible":// 批量屏蔽(更新is_visible=0)updateBatchStatus(ids, CommentStatus.INVISIBLE.getStatus());break;default:return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); // 非法操作類型}flushRedis(); // 操作后刷新緩存return ResponseEntity.ok().build();} catch (Exception e) {log.error("批量操作失敗:{}", e.getMessage(), e); // 記錄完整異常棧return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();}
}/*** 批量更新評論狀態*/
private void updateBatchStatus(List<Integer> ids, Integer status) {UpdateWrapper<Comments> wrapper = new UpdateWrapper<>();wrapper.in(Comments::getId, ids) // ID在指定列表中.set(Comments::getIsVisible, status) // 更新is_visible字段.set(Comments::getUpdateTime, new Date()); // 更新時間戳commentService.update(wrapper); // MyBatis-Plus的update方法(自動生成UPDATE語句)
}
(2)事務與原子性保證
- 事務注解:在Service層方法添加
@Transactional(rollbackFor = Exception.class)
,確保批量操作要么全部成功,要么全部回滾。 - 冪等性設計:通過數據庫唯一索引(如
id
字段)避免重復操作,前端按鈕添加防重復點擊(disabled
屬性)。 - 異常處理:捕獲
SqlException
等數據庫異常,記錄日志并返回500狀態碼,前端提示“操作失敗,請重試”。
3. 緩存策略與數據一致性
(1)Redis緩存刷新
private void flushRedis() {// 使用RedisCallback執行原生Redis命令(清空當前數據庫)redisTemplate.execute((RedisCallback<Void>) connection -> {connection.flushDb(); // 清空DB(根據業務需求可改為按Key前綴刪除)return null;});
}
設計考量:
- 全量刷新:評論數據變更后(增/刪/改),清空整個Redis數據庫,確保下次查詢獲取最新數據。適用于數據實時性要求高的場景。
- 優化方向:若數據量極大,可改為按
blog_id
等維度刪除特定Key(如comments:blog:123
),減少緩存擊穿風險。
(2)緩存重建策略
- 懶加載:首次訪問時若緩存不存在,從數據庫加載并寫入緩存(設置過期時間,如30分鐘)。
- 預加載:定時任務(如Quartz)每隔1小時刷新熱門博客的評論緩存,減輕高峰期數據庫壓力。
三、前端實現細節與交互優化
1. 響應式布局的CSS實現
(1)媒體查詢與布局切換
/* PC端表格視圖(默認顯示) */
.table-container {display: block;
}/* 移動端卡片視圖(默認隱藏) */
.comments-cards {display: none;
}/* 媒體查詢:屏幕寬度≤768px時切換為卡片視圖 */
@media (max-width: 768px) {.table-container {display: none; /* 隱藏表格 */}.comments-cards {display: flex; /* 顯示卡片 */flex-direction: column;gap: 20px;}
}
關鍵技術點:
- 使用
display: none
/display: flex
控制視圖切換,避免visibility: hidden
導致的布局抖動。 - Flex布局(
flex-direction: column
)實現卡片的垂直排列,適配移動端屏幕。
(2)固定列寬與表格滾動
.table-container table {width: 100%;min-width: 1000px; /* 最小寬度防止內容擠壓 */table-layout: fixed; /* 固定列寬(不根據內容自動擴展) */border-collapse: separate; /* 邊框分離(解決邊框重疊問題) */border-spacing: 0; /* 邊框間距為0 */
}/* 列寬定義(示例) */
.table-container th:nth-child(1) { width: 40px; } /* 復選框列 */
.table-container th:nth-child(2) { width: 120px; } /* 昵稱列 */
.table-container th:nth-child(3) { width: 40%; } /* 內容列 */
.table-container th:nth-child(4) { width: 180px; } /* 時間列 */
.table-container th:nth-child(5) { width: 120px; } /* IP屬地列 */
.table-container th:nth-child(6) { width: 100px; } /* 操作列 */
優勢:
- 固定列寬避免表格在PC端因內容過長導致列寬錯亂。
table-layout: fixed
提升渲染性能(瀏覽器無需計算列寬)。
2. 交互增強與用戶體驗
(1)評論內容展開/收起
<!-- 桌面端內容容器 -->
<div class="content-cell"><div th:attr="id='desktop-content-${comment.id}'" class="comment-content desktop-comment"th:text="${comment.content}"><!-- Thymeleaf渲染原始內容 --></div><div class="toggle-expand" th:attr="data-id=${comment.id}, data-type='desktop'"onclick="toggleExpand(this)"><i class="fas fa-angle-down"></i> 展開完整內容</div>
</div><!-- 移動端內容容器 -->
<div class="comment-card" th:each="comment : ${commentPage.records}"><div class="card-cell"><div th:attr="id='mobile-content-${comment.id}'" class="comment-content mobile-comment"th:text="${comment.content}"></div><div class="toggle-expand" th:attr="data-id=${comment.id}, data-type='mobile'"onclick="toggleExpand(this)"><i class="fas fa-angle-down"></i> 展開完整內容</div></div>
</div>
// 展開/收起功能實現
function toggleExpand(element) {const id = element.dataset.id; // 獲取評論ID(data-id屬性)const type = element.dataset.type; // 獲取設備類型(desktop/mobile)const contentEl = document.getElementById(`${type}-content-${id}`); // 定位內容元素// 切換展開狀態contentEl.classList.toggle('expanded');// 更新按鈕文本和圖標if (contentEl.classList.contains('expanded')) {element.innerHTML = '<i class="fas fa-angle-up"></i> 收起內容'; // 展開后顯示收起按鈕} else {element.innerHTML = '<i class="fas fa-angle-down"></i> 展開內容'; // 未展開時顯示展開按鈕}
}
CSS動畫優化:
/* 展開/收起的過渡效果 */
.comment-content {transition: max-height 0.3s ease-in-out; /* 平滑過渡高度變化 */overflow: hidden; /* 隱藏超出部分 */
}.desktop-comment:not(.expanded) {max-height: 60px; /* 未展開時最大高度 */
}.mobile-comment:not(.expanded) {max-height: 80px; /* 移動端未展開時最大高度 */
}.comment-content.expanded {max-height: none; /* 展開時無高度限制 */
}
(2)批量操作的按鈕狀態管理
// 監聽復選框變化,更新批量按鈕狀態
const rowCheckboxes = document.querySelectorAll('.row-checkbox');
const batchButtons = document.querySelectorAll('#batchDelete, #batchApprove, #batchReject');function updateBatchButtonState() {const hasChecked = Array.from(rowCheckboxes).some(checkbox => checkbox.checked);batchButtons.forEach(button => {button.disabled = !hasChecked; // 無選中項時禁用按鈕button.setAttribute('aria-disabled', !hasChecked); // 輔助技術支持});
}// 為每個復選框添加change事件監聽
rowCheckboxes.forEach(checkbox => {checkbox.addEventListener('change', updateBatchButtonState);
});// 全選/取消全選時同步狀態
document.getElementById('headerSelectAll').addEventListener('change', function() {const isChecked = this.checked;rowCheckboxes.forEach(checkbox => checkbox.checked = isChecked);updateBatchButtonState();
});
用戶體驗優化:
- 按鈕禁用狀態通過
disabled
屬性和opacity: 0.6
樣式雙重提示。 - 使用
aria-disabled
屬性增強屏幕閱讀器的可訪問性。
四、安全防護與性能優化
1. 安全防護措施
(1)輸入驗證與過濾
-
后端校驗:使用
@RequestParam(required = false)
標記可選參數,避免null
值注入。 -
關鍵詞過濾:對用戶輸入的keyword進行敏感詞過濾(示例):
if (StringUtils.hasText(keyword)) {keyword = sensitiveWordFilter.replace(keyword); // 替換敏感詞queryWrapper.and(wrapper -> wrapper.like(Comments::getNickname, keyword).or().like(Comments::getContent, keyword)); }
(2)XSS防護
-
Thymeleaf自動轉義:默認開啟HTML轉義(
${comment.content}
會自動轉義<
、>
等字符)。 -
手動轉義:對于需要輸出原始HTML的場景(如富文本編輯器內容),使用th:utext并配合自定義過濾器:
// 自定義XSS過濾器(配置在WebMvcConfigurer中) @Bean public FilterRegistrationBean<XssFilter> xssFilter() {FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>();registration.setFilter(new XssFilter());registration.addUrlPatterns("/*"); // 攔截所有請求registration.setName("xssFilter");registration.setOrder(Ordered.HIGHEST_PRECEDENCE); // 最高優先級return registration; }
(3)CSRF防護
@PostMapping("/batchOperation")
public ResponseEntity<Void> batchOperation(@RequestParam("ids[]") List<Integer> ids,@RequestParam("operation") String operation,HttpServletRequest request) {// 驗證CSRF Token(Spring Security自動處理)CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());if (csrfToken == null) {return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); // 無Token拒絕訪問}// 驗證Token是否匹配(Spring Security自動完成,此處僅為示例)String requestToken = request.getHeader(csrfToken.getHeaderName());if (!csrfToken.getToken().equals(requestToken)) {return ResponseEntity.status(HttpStatus.FORBIDDEN).build();}// ...業務邏輯
}
2. 性能優化策略
(1)數據庫優化
-
索引設計:在comments表創建聯合索引(is_visible, create_time DESC),加速分頁查詢:
CREATE INDEX idx_comments_status_time ON comments(is_visible, create_time DESC);
-
慢查詢監控:開啟MySQL慢查詢日志(
slow_query_log=ON
),定位執行時間超過1秒的SQL。
(2)前端性能優化
-
虛擬滾動:對于超大數據量(如10萬條評論),使用
vue-virtual-scroller
或react-virtualized
實現虛擬滾動,僅渲染可見區域的DOM節點。 -
防抖搜索:對關鍵詞輸入框添加防抖(300ms),避免頻繁觸發搜索:
let searchTimeout; document.getElementById('keywordFilter').addEventListener('input', (e) => {clearTimeout(searchTimeout);searchTimeout = setTimeout(() => {const form = e.target.closest('form');form.submit(); // 300ms無輸入后提交表單}, 300); });
五、擴展功能與未來規劃
1. 待實現功能
- 審核工作流:增加多級審核(如編輯初審→管理員終審),通過狀態機(
0=待編輯初審
,1=待管理員終審
,2=已發布
)管理流程。 - 垃圾評論過濾:集成AI模型(如TensorFlow Lite)識別垃圾評論(廣告、辱罵等),自動標記或屏蔽。
- 實時通知:使用WebSocket實現新評論提醒(管理員收到WebSocket消息,前端彈出提示)。
2. 技術升級方向
- 后端:遷移至Spring Boot 3.x,支持GraalVM原生編譯,提升啟動速度。
- 前端:引入Vue 3或React,實現更復雜的交互(如拖拽排序、富文本編輯)。
- 數據庫:對于超大規模數據(如百萬級評論),考慮分庫分表(ShardingSphere)或使用列式數據庫(ClickHouse)加速查詢。
六、總結
本系統通過Spring Boot + MyBatis-Plus + Thymeleaf的技術組合,構建了一個高效、安全的博客評論管理系統。核心設計思想包括:
- 動態SQL與分頁優化:通過MyBatis-Plus的
LambdaQueryWrapper
簡化條件構造,結合分頁插件提升查詢性能。 - 響應式布局:基于媒體查詢和Flex布局,實現PC與移動端的無縫切換。
- 批量操作與事務安全:通過MyBatis-Plus的批量API和Spring的事務管理,保證數據一致性。
- 多層安全防護:輸入驗證、XSS過濾、CSRF防護構建全方位安全體系。
未來可結合業務需求擴展審核流程、智能過濾等功能,同時關注新技術趨勢(如Serverless、邊緣計算)進一步優化系統性能。