建校園疫情管理系統(tǒng)的完整實(shí)踐)
簡介這是一套基于Spring Boot、MyBatis-Plus與Layui的校園疫情管理系統(tǒng)面向高校信息化管理人員、Java全棧學(xué)習(xí)者以及畢業(yè)設(shè)計(jì)開發(fā)者。系統(tǒng)圍繞疫情背景下的校園管理需求完成健康數(shù)據(jù)采集、審批流轉(zhuǎn)、多角色權(quán)限管控等核心功能。資源包為RAR壓縮格式共850個(gè)文件整體容量約58.11MB主要文件包括Java源碼、XML映射與配置文件、HTML頁面、CSS樣式、JavaScript腳本、圖片圖標(biāo)以及SQL數(shù)據(jù)庫腳本等目錄結(jié)構(gòu)完整導(dǎo)入IDE即可運(yùn)行或二次開發(fā)。在功能層面系統(tǒng)接入了騰訊API可展示中國疫情確診分布地圖和實(shí)時(shí)疫情新聞并通過餅圖、折線圖、柱狀圖對(duì)疫情數(shù)據(jù)進(jìn)行可視化集成Shiro安全框架實(shí)現(xiàn)學(xué)生、教師、院系管理員等不同角色的動(dòng)態(tài)菜單權(quán)限分配。業(yè)務(wù)模塊覆蓋健康打卡、疫情數(shù)據(jù)管理、院系班級(jí)管理、核酸檢測(cè)管理、疫苗接種管理以及學(xué)生請(qǐng)假串行審批等同時(shí)支持Excel拖拽上傳與導(dǎo)出具備條件查詢與分頁的增刪改查能力。資源包已有1122人學(xué)習(xí)適合用作畢業(yè)設(shè)計(jì)參考也可幫助開發(fā)者深入理解RBAC權(quán)限設(shè)計(jì)與數(shù)據(jù)可視化實(shí)現(xiàn)。1. 校園疫情管理系統(tǒng)為什么還在用 SpringBoot 三件套校園疫情管理系統(tǒng)不只是一張今日健康打卡表。它的真實(shí)形態(tài)是學(xué)生每日上報(bào)體溫和行程、輔導(dǎo)員審核異常、校醫(yī)跟進(jìn)隔離狀態(tài)、管理員看全院統(tǒng)計(jì)大屏。這句話拆開之后你會(huì)發(fā)現(xiàn)它同時(shí)踩中了三個(gè)后端項(xiàng)目最常見的問題數(shù)據(jù)寫入頻繁但邏輯簡單、權(quán)限層級(jí)多但又不適合上重型工作流、統(tǒng)計(jì)查詢要快但表結(jié)構(gòu)天天變。SpringBoot MybatisPlus layui 這套組合恰恰把這三件事的性價(jià)比做到極致SpringBoot 管好接口生命周期和依賴注入MybatisPlus 用 ActiveRecord 風(fēng)格把單表 CRUD 壓到幾乎沒有樣板代碼layui 自帶表格、彈層、日期組件不需要前端構(gòu)建鏈就能在真實(shí)服務(wù)器上跑起來。對(duì)做畢設(shè)、做課設(shè)、做中小型校內(nèi)系統(tǒng)的開發(fā)者來說這是最快能落地的路線。下面要寫的不是項(xiàng)目介紹而是一套可以照著抄的落地方案從表設(shè)計(jì)、代碼分層、layui 表格對(duì)接到分頁失效、日期控件限制、自動(dòng)建表這幾個(gè)高頻坑一次說清。2. SpringBoot 與 MybatisPlus 的數(shù)據(jù)層設(shè)計(jì)先定表再寫代碼2.1 表結(jié)構(gòu)設(shè)計(jì)的取舍疫情管理系統(tǒng)的核心數(shù)據(jù)無非四類學(xué)生基礎(chǔ)信息、每日健康上報(bào)、出校審批、異常記錄。這四類數(shù)據(jù)在真實(shí)業(yè)務(wù)里的特點(diǎn)是字段多但都不復(fù)雜、狀態(tài)流轉(zhuǎn)明確、查詢條件基本都是學(xué)院 日期 狀態(tài)。所以表設(shè)計(jì)不需要過度范式化反而應(yīng)該把高頻查詢字段直接冗余到主表里。CREATE TABLE tb_student ( id bigint(20) NOT NULL AUTO_INCREMENT, student_no varchar(32) NOT NULL COMMENT 學(xué)號(hào), name varchar(32) NOT NULL, college varchar(64) DEFAULT NULL COMMENT 學(xué)院, grade varchar(16) DEFAULT NULL COMMENT 年級(jí), phone varchar(16) DEFAULT NULL, status tinyint(1) DEFAULT 1 COMMENT 1在校 0離校, PRIMARY KEY (id), UNIQUE KEY uk_student_no (student_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT學(xué)生表; CREATE TABLE tb_health_report ( id bigint(20) NOT NULL AUTO_INCREMENT, student_id bigint(20) NOT NULL, report_date date NOT NULL COMMENT 上報(bào)日期, temperature decimal(4,2) DEFAULT NULL COMMENT 體溫, health_code varchar(16) DEFAULT NULL COMMENT 綠碼/黃碼/紅碼, is_abnormal tinyint(1) DEFAULT 0 COMMENT 1異常 0正常, remark varchar(255) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_date_status (report_date, is_abnormal) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT健康上報(bào)表;字段不需要一個(gè)都不剩地照抄但兩個(gè)點(diǎn)要注意一是report_date用 date 而不是 datetime因?yàn)闃I(yè)務(wù)維度就是按天上報(bào)用 datetime 反而讓統(tǒng)計(jì)查詢多一層轉(zhuǎn)換二是唯一索引必須加在(student_id, report_date)上否則學(xué)生一天重復(fù)提交會(huì)留臟數(shù)據(jù)。MybatisPlus 的saveOrUpdate無法天然防重復(fù)最終要靠這個(gè)唯一索引兜底。2.2 SpringBoot 集成 MybatisPlus 的最小配置MybatisPlus 3.5.x 是目前主流版本SpringBoot 2.7.x 對(duì)應(yīng) JDK8 項(xiàng)目足夠。在pom.xml里只需要引入兩個(gè)依賴dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId /dependency不需要額外引入 mybatis 依賴starter 會(huì)自動(dòng)帶。application.yml里重點(diǎn)配置兩項(xiàng)邏輯刪除和分頁插件。邏輯刪除是疫情管理系統(tǒng)的剛需因?yàn)閷W(xué)生異動(dòng)和上報(bào)記錄不能物理刪除否則事后審計(jì)沒法追溯。spring: datasource: url: jdbc:mysql://localhost:3306/campus_epidemic?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: ${DB_PASSWORD:root} jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl密碼這一行用了${DB_PASSWORD:root}的占位符形式意思是環(huán)境變量里有 DB_PASSWORD 就用它沒有就 fallback 到 root。這個(gè)習(xí)慣尤其在部署到服務(wù)器后非常重要因?yàn)閹炖锟赡苡袑W(xué)生手機(jī)號(hào)和身份證號(hào)明文密碼寫進(jìn) yml 再推到代碼倉庫就是安全事故。如果還想更穩(wěn)妥一點(diǎn)用 Jasypt 把密碼整體加密成密文串springboot yml密文是這個(gè)方向最常見的檢索詞。不過對(duì)于小項(xiàng)目環(huán)境變量占位符已經(jīng)夠用。2.3 Service 層代碼別把所有邏輯堆在 Controller一個(gè)常見壞味道是 Controller 里直接注入 Mapper 然后寫 CRUD。疫情管理系統(tǒng)的業(yè)務(wù)規(guī)則比 CRUD 多一點(diǎn)比如學(xué)生只能改自己當(dāng)天的上報(bào)輔導(dǎo)員只能看本院數(shù)據(jù)這些必須收斂到 Service 層。MybatisPlus 的IService接口加上ServiceImpl實(shí)現(xiàn)類的寫法public interface HealthReportService extends IServiceHealthReport { boolean reportToday(HealthReport report); PageHealthReportVO pageWithStudent(PageHealthReport page, String college, Integer isAbnormal); } Service public class HealthReportServiceImpl extends ServiceImplHealthReportMapper, HealthReport implements HealthReportService { Override public boolean reportToday(HealthReport report) { report.setReportDate(LocalDate.now()); report.setIsAbnormal(report.getTemperature() ! null report.getTemperature().compareTo(new BigDecimal(37.3)) 0 ? 1 : 0); return saveOrUpdate(report); } }這段代碼的核心邏輯不在saveOrUpdate而在setIsAbnormal之前的那行判斷。體溫是否異常是一個(gè)業(yè)務(wù)規(guī)則把它放在 Service 層意味著將來調(diào)整閾值時(shí)只改一個(gè)地方。前端拿到上報(bào)成功與否就夠了不應(yīng)該由前端告訴后端這個(gè)人異常了因?yàn)榍岸丝梢员焕@過。另外注意report.setReportDate(LocalDate.now())后端強(qiáng)制取服務(wù)器日期不信任客戶端傳的日期否則學(xué)生會(huì)改手機(jī)系統(tǒng)時(shí)間補(bǔ)填昨天的記錄。3. layui 表格與后端接口的聯(lián)調(diào)日期、分頁、行點(diǎn)擊事件3.1 layui 的 table 模塊和 SpringBoot 分頁接口對(duì)齊格式layui 的 table 模塊默認(rèn)請(qǐng)求方式是把page和limit作為普通參數(shù)發(fā)到后端返回格式要求是{code:0, msg:, count:100, data:[]}。這個(gè)約定和 SpringBoot 的 Page 對(duì)象天然匹配只需在 Controller 里做一個(gè)組裝不要直接把 MybatisPlus 的 Page 對(duì)象返回出去。RestController RequestMapping(/api/report) public class HealthReportController { Autowired private HealthReportService reportService; GetMapping(/page) public MapString, Object page(RequestParam(defaultValue 1) long page, RequestParam(defaultValue 10) long limit, String college, Integer isAbnormal) { PageHealthReport p new Page(page, limit); PageHealthReportVO result reportService.pageWithStudent(p, college, isAbnormal); MapString, Object map new HashMap(); map.put(code, 0); map.put(msg, ); map.put(count, result.getTotal()); map.put(data, result.getRecords()); return map; } }把 Page 對(duì)象轉(zhuǎn)成 Map 返回一方面是為了匹配 layui 的協(xié)議另一方面是避免把 MybatisPlus 的pages、current、optimizeCountSql這些內(nèi)部字段暴露給前端。defaultValue要顯式寫上否則前端第一次加載表格沒傳 page 時(shí)SpringBoot 會(huì)直接 400。layui 表格默認(rèn)每頁 10 條但統(tǒng)計(jì)頁通常需要更大所以前端 render 里我會(huì)顯式寫明page: true, limit: 20。3.2 layui 日期控件限制最大日期為今天健康上報(bào)表格里日期范圍查詢是最常用篩選條件。layui 的laydate組件默認(rèn)允許選未來日期如果不限制用戶選到明天甚至下個(gè)月查詢結(jié)果永遠(yuǎn)為空而且會(huì)讓人以為是 Bug。限制最大日期的寫法是laydate.render({ elem: #reportDateRange, type: date, range: true, max: laydate.now() });max: laydate.now()是最容易被忽略的一行。laydate 的max參數(shù)支持三種格式固定字符串2025-12-31、函數(shù)laydate.now()、以及帶偏移的寫法max: -1。laydate.now()取的是客戶端本地時(shí)間而查詢接口對(duì)日期范圍的上限約束在后端還要再校驗(yàn)一次。if (endDate.isAfter(LocalDate.now())) { throw new BizException(結(jié)束日期不能晚于今天); }前端限制只是體驗(yàn)優(yōu)化后端校驗(yàn)才是數(shù)據(jù)正確性的保障。凌晨跨天時(shí)最容易出問題前端頁面開著沒過期但到了零點(diǎn)之后now()已經(jīng)是新的一天用戶選了昨天作為結(jié)束日期前端不會(huì)攔截后端如果不校驗(yàn)就直接放行了。所以在后端寫這個(gè)校驗(yàn)不是形式主義。3.3 table 列點(diǎn)擊事件不只是加個(gè)監(jiān)聽器需求里常見的點(diǎn)擊學(xué)生姓名彈出詳情放在 layui 里有兩種做法。一種是在cols配置里給列加templet模板把名字渲染成帶lay-eventdetail的a標(biāo)簽然后在table.on(tool(filter), ...)回調(diào)里操作。另一種是cellClick事件區(qū)別在于前者只能命中設(shè)置了lay-event的 HTML 元素后者可以精確到某個(gè)單元格。實(shí)際開發(fā)中我推薦第一種。table.render({ elem: #reportTable, url: /api/report/page, page: true, cols: [[ { field: studentNo, title: 學(xué)號(hào), width: 120 }, { field: name, title: 姓名, width: 100, templet: a classlayui-table-link lay-eventstudentDetail{{ d.name }}/a }, { field: temperature, title: 體溫, width: 80, templet: function(d) { if (d.temperature ! null Number(d.temperature) 37.3) { return span stylecolor:#FF5722 d.temperature ℃/span; } return d.temperature ℃; }}, { field: createTime, title: 上報(bào)時(shí)間, width: 170 } ]] }); table.on(tool(reportTable), function(obj) { if (obj.event studentDetail) { showStudentModal(obj.data.studentId); } });templet里的d就是當(dāng)前行數(shù)據(jù)用字符串模板直接拼 HTML或者用函數(shù)方式返回帶樣式的結(jié)構(gòu)。體溫正紅色顯示這個(gè)細(xì)節(jié)可以一眼篩出異常記錄比多寫一列isAbnormal再判斷更直觀。注意tool監(jiān)聽的lay-filter值要和table.render里的elem對(duì)應(yīng)如果 render 時(shí)沒有寫id: reportTable事件綁定是無效的。事件名和過濾器的對(duì)應(yīng)關(guān)系是 layui 初學(xué)者最容易卡殼的地方。4. 單表 500 條限制、分頁失效的表單提交問題4.1 超過 500 條數(shù)據(jù) MybatisPlus 單頁查不出來先確認(rèn)是不是 SQL 寫錯(cuò)接觸mybatisplus單頁500條限制這個(gè)熱搜詞反映了一個(gè)常見現(xiàn)象列表頁翻到第二頁之后數(shù)據(jù)重復(fù)或缺失。先說結(jié)論MybatisPlus 本身沒有 500 條分頁限制出問題的地方大多在PaginationInnerInterceptor沒有注冊(cè)。分頁插件必須顯式聲明Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }DbType.MYSQL會(huì)自動(dòng)把分頁改寫為LIMIT沒配這個(gè) Bean 時(shí)Page傳進(jìn)去也是全表查詢。還有一個(gè)隱蔽場(chǎng)景自定義 SQL 里的分頁失效。當(dāng)你在 Mapper.xml 里手寫了selectList或selectPage對(duì)應(yīng)的方法并且拼接了${ew.customSqlSegment}同時(shí)查詢條件里帶了last或自己寫了LIMIT就會(huì)出現(xiàn)分頁和自定義條件互相覆蓋。select idselectReportPageWithCollege resultTypecom.example.vo.HealthReportVO SELECT r.*, s.name, s.college FROM tb_health_report r LEFT JOIN tb_student s ON r.student_id s.id where if testcollege ! null and college ! AND s.college #{college} /if /where ORDER BY r.create_time DESC /select分頁攔截器是攔截執(zhí)行層面的 SQL把整條查詢包一層COUNT再追加LIMIT。上面的自定義 SQL 帶LEFT JOIN如果發(fā)現(xiàn)總數(shù)對(duì)但列表數(shù)據(jù)偏移多半是 COUNT 語句里把 LEFT JOIN 關(guān)聯(lián)出來的重復(fù)行算進(jìn)去了。排查方法很簡單把 MybatisPlus 日志打開看打印出來的 COUNT SQL 是什么單獨(dú)拿去數(shù)據(jù)庫執(zhí)行一眼就能看出 count(*) 和表實(shí)際行數(shù)對(duì)不上。4.2 表單重復(fù)提交導(dǎo)致一天多條上報(bào)記錄學(xué)生端最常見的一個(gè) Bug 是點(diǎn)提交后接口響應(yīng)慢心急再點(diǎn)一次結(jié)果生成了兩條同一天的上報(bào)。這個(gè)問題在reportToday這個(gè)方法里用saveOrUpdate是防不住的因?yàn)榈谝淮握?qǐng)求和第二次請(qǐng)求到達(dá) Service 時(shí)數(shù)據(jù)庫中都不存在當(dāng)天記錄兩個(gè)線程都走 INSERT。數(shù)據(jù)庫唯一索引uk_student_report_date會(huì)攔截第二次 INSERT 并拋 DuplicateKeyException。public boolean reportToday(HealthReport report) { try { return saveOrUpdate(report); } catch (DuplicateKeyException e) { // 再次嘗試更新避免直接報(bào)錯(cuò)給用戶 report.setId(getBaseMapper().selectOne(new LambdaQueryWrapperHealthReport() .eq(HealthReport::getStudentId, report.getStudentId()) .eq(HealthReport::getReportDate, LocalDate.now())).getId()); return updateById(report); } }前端按鈕加上lay-disable狀態(tài)在 ajax 回調(diào)前禁止重復(fù)點(diǎn)擊只是緩解。真正的防線是唯一索引加 DuplicateKeyException 捕獲二者缺一不可。注意捕獲異常后的二次更新里report.getId()可能是 null需要先從庫里把主鍵查出來再updateById這也是為什么建議保留唯一索引而不是去掉。4.3 出入校審批的狀態(tài)流轉(zhuǎn)不要用 int 裸奔出校審批的狀態(tài)字段如果直接存0/1/2過三個(gè)月沒人記得 1 是待輔導(dǎo)員審批還是待校醫(yī)審批。常見做法是用枚舉類管理public enum ApproveStatus { PENDING(0, 待審批), PASS(1, 已通過), REJECT(2, 已駁回); }MybatisPlus 默認(rèn)的枚舉映射需要加EnumValue注解。但更簡單的方案是直接存字符串狀態(tài)碼PENDING / PASS / REJECT可讀性比數(shù)字好得多而且在 SQL 排查時(shí)一眼能看懂。這一層算是少即是多——復(fù)雜狀態(tài)機(jī)對(duì)校內(nèi)審批場(chǎng)景屬于過度設(shè)計(jì)status加update_time加audit_remark就足夠?qū)徲?jì)了。5. 部署前的三個(gè)必查點(diǎn)字段自動(dòng)建表、接口安全、統(tǒng)計(jì)口徑5.1 要不要讓 MybatisPlus 自動(dòng)建表搜索熱詞里出現(xiàn)表不存在自動(dòng)建表說明不少項(xiàng)目在開發(fā)階段被建表 SQL 折磨過。MybatisPlus 本身沒有內(nèi)置 DDL 自動(dòng)建表能力它只管 ORM。常見做法有兩種一是集成mybatis-plus-ddl這類插件啟動(dòng)時(shí)比對(duì)實(shí)體類注解建表二是在application.yml配置spring.sql.init.mode: always配合schema.sql在啟動(dòng)時(shí)執(zhí)行。我的建議是第二種因?yàn)?DDL 腳本可以被版本管理改動(dòng)記錄清楚。spring: sql: init: mode: always schema-locations: classpath:db/schema.sql continue-on-error: falseschema.sql里寫CREATE TABLE IF NOT EXISTS啟動(dòng)報(bào)錯(cuò)時(shí)continue-on-error設(shè)為 false 可以快速暴露問題。生產(chǎn)環(huán)境務(wù)必把mode改成never否則每次啟動(dòng)都可能覆蓋變更。5.2 接口層面的最小安全策略校園系統(tǒng)在公網(wǎng)暴露的情況不多但一旦暴露最先被掃描的就是/actuator/heapdump、/api/report/page這種無鑒權(quán)接口。最輕量的做法是加一個(gè) HandlerInterceptor 做 token 校驗(yàn)public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || !TokenStore.isValid(token)) { response.setStatus(401); response.getWriter().write({\code\:401,\msg\:\unauthorized\}); return false; } return true; } }項(xiàng)目里如果引了 spring-boot-starter-actuator建議把暴露端點(diǎn)全部關(guān)掉或者至少加management.endpoints.web.exposure.include: health避免 heapdump 敏感信息被拉走。技術(shù)上你不是在處理理論威脅而是在處理真實(shí)發(fā)生過的事故——這個(gè)風(fēng)險(xiǎn)點(diǎn)已經(jīng)不屬于可選項(xiàng)了。5.3 統(tǒng)計(jì)報(bào)表的 Slow SQL 排查管理員首頁的今日上報(bào)率各學(xué)院異常人數(shù)這類統(tǒng)計(jì)優(yōu)化空間比想象中大。一個(gè)常見壞味道是前端加載頁面時(shí)同時(shí)發(fā)十幾個(gè)統(tǒng)計(jì)請(qǐng)求每個(gè)請(qǐng)求都執(zhí)行一次GROUP BY COUNT數(shù)據(jù)庫壓力全在運(yùn)行時(shí)的計(jì)算上。正確做法是加一張tb_report_statistics日匯總表每晚定時(shí)任務(wù)統(tǒng)計(jì)一次前端只查這張表Component public class StatisticTask { Scheduled(cron 0 10 0 * * ?) public void generateYesterdayStatistic() { ListDailyStatistic list reportMapper.selectDailyGroupByCollege(LocalDate.now().minusDays(1)); statisticService.saveBatch(list); } }日匯總表加定時(shí)任務(wù)是一個(gè)從功能能用到扛得住早高峰的分水嶺。如果不想加表至少給統(tǒng)計(jì) SQL 涉及的report_date和college建聯(lián)合索引。排查慢 SQL 用 MySQL 的slow_query_log即可定位到具體 SQL 后用EXPLAIN看 type 字段是否是ref以上而不是ALL。6. 一個(gè)順手的小技巧用 MybatisPlus 的 condition 參數(shù)收斂查詢條件LambdaQueryWrapper的eq方法有一個(gè)重載接收boolean condition參數(shù)這個(gè)參數(shù)在接 layui 表格篩選時(shí)特別好用。前端可能傳college空字符串、isAbnormalnull如果不處理就會(huì)變成WHERE college 導(dǎo)致查不到數(shù)據(jù)。常見寫法是每個(gè)字段都if判斷一次代碼又臭又長。public PageHealthReportVO pageWithStudent(PageHealthReport page, String college, Integer isAbnormal) { LambdaQueryWrapperHealthReport wrapper new LambdaQueryWrapper(); wrapper.eq(StringUtils.hasText(college), HealthReport::getCollege, college) .eq(isAbnormal ! null, HealthReport::getIsAbnormal, isAbnormal) .orderByDesc(HealthReport::getReportDate); return baseMapper.selectReportPageWithCollege(page, wrapper); }.eq(condition, column, value)里 condition 為 false 時(shí)整段條件不拼接到 SQL。這個(gè) API 是 MybatisPlus 比較實(shí)用的設(shè)計(jì)之一能讓查詢接口少寫 30% 的 if 判斷。注意StringUtils.hasText是 Spring 提供的工具方法如果你在非 Spring 環(huán)境測(cè)試這段代碼換成college ! null !college.isEmpty()也一樣。另外一個(gè)細(xì)節(jié)是orderByDesc要放在最后否則LIMIT分頁在排序字段有相同值時(shí)會(huì)出現(xiàn)翻頁數(shù)據(jù)重疊這也是為什么要在排序字段后面追加一個(gè)唯一字段比如orderByDesc(HealthReport::getReportDate).orderByDesc(HealthReport::getId)。加上唯一字段兜底后翻頁重復(fù)率基本歸零。整套方案寫到這里從表設(shè)計(jì)到前端聯(lián)調(diào)再到部署排錯(cuò)核心鏈路已經(jīng)完整覆蓋。剩余的工作就是建兩個(gè)測(cè)試賬號(hào)跑一遍學(xué)生上報(bào)—輔導(dǎo)員審核—管理員看統(tǒng)計(jì)的主流程把異常體溫閾值從 37.3 改成 37.5 驗(yàn)一下規(guī)則是否真的只改一處。運(yùn)行mvn spring-boot:run把項(xiàng)目拉起來打開本地 8080 端口剩下的交互細(xì)節(jié)在日志里見真章。本文還有配套的精品資源點(diǎn)擊獲取