級(jí)失蹤人員信息管理系統(tǒng))
做企業(yè)級(jí)失蹤人員信息發(fā)布與管理系統(tǒng)源碼項(xiàng)目有一件事讓我印象很深很多人上手這類系統(tǒng)時(shí)最容易低估的是審核流轉(zhuǎn)和數(shù)據(jù)閉環(huán)這兩塊反而把大量時(shí)間花在了頁(yè)面上。實(shí)際上一套真正能用的管理系統(tǒng)核心在于把公告發(fā)布、人員登記、線索上報(bào)、審核流轉(zhuǎn)、狀態(tài)變更這條鏈路串起來并且每一步都有跡可循。我做的這套項(xiàng)目基于SpringBootVueMyBatisMySQL架構(gòu)前后端分離源碼完整既能直接部署演示也適合在此基礎(chǔ)上做二次開發(fā)。文章里我會(huì)從需求本質(zhì)、技術(shù)選型、數(shù)據(jù)庫(kù)設(shè)計(jì)、后端接口實(shí)現(xiàn)、前端交互細(xì)節(jié)講到部署安全與排坑復(fù)盤盡量把文檔里不會(huì)寫的東西也講透。如果你正在做政務(wù)信息化、公益互助平臺(tái)、尋人相關(guān)方向的項(xiàng)目或者準(zhǔn)備用這類題目做畢業(yè)設(shè)計(jì)、企業(yè)實(shí)習(xí)項(xiàng)目這篇文章可以直接作為復(fù)現(xiàn)和改造的參考。1. 這類系統(tǒng)的需求本質(zhì)不只是發(fā)一個(gè)公告那么簡(jiǎn)單先說一句大實(shí)話失蹤人員信息管理系統(tǒng)從功能列表上看似乎就是發(fā)布尋人啟事 管理線索聽起來輕松但落到真實(shí)業(yè)務(wù)里痛點(diǎn)比想象中多得多。1.1 三類參與者三個(gè)層面的真實(shí)痛點(diǎn)系統(tǒng)的使用者大致分為三類普通訪客、審核人員、系統(tǒng)管理員。不同角色對(duì)系統(tǒng)的期望是完全不同的這直接決定了功能設(shè)計(jì)的優(yōu)先級(jí)。普通訪客的痛點(diǎn)是信息太散。家人走失之后親友通常第一反應(yīng)是發(fā)朋友圈、貼紙質(zhì)告示、求助本地社區(qū)但這些渠道各自為戰(zhàn)信息無法匯總。平臺(tái)要做的不是再增加一個(gè)孤島而是提供一個(gè)統(tǒng)一入口讓所有公告集中展示、集中檢索并且能在線提交線索。審核人員的痛點(diǎn)是核實(shí)難、狀態(tài)更新難。走失信息涉及大量個(gè)人隱私一旦發(fā)布出去就是全網(wǎng)可見如果沒有審核環(huán)節(jié)假消息和過期消息會(huì)迅速消耗公眾信任。審核員最需要的是待審隊(duì)列 狀態(tài)流轉(zhuǎn)讓每一條信息從提交、審核、發(fā)布、找到、歸檔都有明確狀態(tài)。管理員的痛點(diǎn)是數(shù)據(jù)無法沉淀。沒有系統(tǒng)支撐時(shí)歷史走失案件的線索往往散落在各個(gè)渠道后期想統(tǒng)計(jì)、比對(duì)、復(fù)盤非常困難。一個(gè)合格的系統(tǒng)應(yīng)該把人和案件的數(shù)據(jù)結(jié)構(gòu)化留存支持按時(shí)間、區(qū)域、年齡、狀態(tài)等多個(gè)維度做統(tǒng)計(jì)分析。這三類痛點(diǎn)的交叉點(diǎn)就是系統(tǒng)的核心需求統(tǒng)一信息源、嚴(yán)格審核流、狀態(tài)可閉環(huán)。1.2 功能模塊與角色權(quán)限怎么劃分基于上面的痛點(diǎn)模塊劃分我建議按人、事、線、審四條線來做功能模塊普通訪客審核人員系統(tǒng)管理員走失人員信息登記可提交查看待審管理全部信息審核與發(fā)布無權(quán)限審核/駁回復(fù)核/撤銷公告展示與檢索查看、搜索查看管理線索上報(bào)與處理提交線索處理線索查看統(tǒng)計(jì)數(shù)據(jù)統(tǒng)計(jì)報(bào)表無權(quán)限有限查看全部維度賬號(hào)與角色管理無權(quán)限無權(quán)限分配賬號(hào)這塊設(shè)計(jì)有一個(gè)很容易踩的坑很多人會(huì)把登記和發(fā)布合并成一個(gè)操作提交之后直接上架。結(jié)果就是系統(tǒng)無法過濾虛假信息一旦被惡意利用后果非常嚴(yán)重。正確的做法是提交是一個(gè)動(dòng)作審核是一個(gè)動(dòng)作發(fā)布又是一個(gè)動(dòng)作三者分離并且每一步都記錄操作日志。1.3 企業(yè)級(jí)三個(gè)字的分量這套系統(tǒng)叫企業(yè)級(jí)不是業(yè)務(wù)量大而是工程規(guī)范上的要求更高Controller 不能堆業(yè)務(wù)邏輯服務(wù)層要做事務(wù)管理數(shù)據(jù)訪問不能靠 JDBC 拼 SQLMyBatis 的 XML 里要支撐動(dòng)態(tài)查詢狀態(tài)字段不能以魔法數(shù)字散落在各處要有狀態(tài)枚舉統(tǒng)一管理所有寫操作要有審計(jì)追蹤誰在什么時(shí)間改了什么一查便知。這些要求決定了整個(gè)項(xiàng)目的代碼結(jié)構(gòu)不是隨意的后面我會(huì)詳細(xì)講落地方式。2. 技術(shù)選型與工程結(jié)構(gòu)SpringBootVueMyBatis這套組合為什么能打技術(shù)選型這塊我不打算做一堆框架對(duì)比直接說結(jié)論和理由因?yàn)檫@套組合在同類管理系統(tǒng)里幾乎是最成熟、資料最全的路線。2.1 后端為什么用 SpringBootSpringBoot 在這類系統(tǒng)里幾乎是標(biāo)準(zhǔn)答案級(jí)別。它有內(nèi)嵌的 Web 容器打包就是一個(gè)可執(zhí)行的 jar部署成本低起步依賴把常見的配置都約定好了開發(fā)時(shí)不需要花大量時(shí)間在環(huán)境整合上生態(tài)里和權(quán)限、緩存、持久層框架的整合方案都已經(jīng)非常成熟。需要注意版本匹配問題如果項(xiàng)目是基于 SpringBoot 2.7.x那 JDK 8 就夠了如果源碼升級(jí)到了 SpringBoot 3.x那必須用 JDK 17 及以上。很多人卡在springboot版本太高導(dǎo)致的啟動(dòng)失敗多半就是 JDK 版本不匹配或者部分第三方依賴還沒適配 3.x。2.2 前端為什么選 Vue 而不是其他框架失蹤人員信息管理系統(tǒng)里有大量多狀態(tài)頁(yè)面 數(shù)據(jù)表格 表單流程的界面邏輯Vue 的組件化和響應(yīng)式數(shù)據(jù)模型非常契合。比起直接用模板引擎渲染頁(yè)面前后端分離之后接口可以同時(shí)復(fù)用給管理后臺(tái)和外部擴(kuò)展。組件生態(tài)也很關(guān)鍵。基于 Vue 的 Element 組件庫(kù)Element UI 對(duì)應(yīng) Vue 2Element Plus 對(duì)應(yīng) Vue 3提供了現(xiàn)成的表格、表單、日期選擇器、分頁(yè)組件管理后臺(tái)開發(fā)效率能翻倍。如果是從零開始搭頁(yè)面不借助組件庫(kù)光是一個(gè)帶篩選和分頁(yè)的表格就能寫幾百行。2.3 MyBatis MySQL 的組合為什么默契MyBatis 最大的價(jià)值是SQL 在手心里不慌。這類系統(tǒng)的查詢條件非常動(dòng)態(tài)按姓名模糊查、按年齡段篩選、按走失時(shí)間范圍查、按區(qū)域查、按狀態(tài)查組合起來可能有幾十種情況。用 MyBatis 的動(dòng)態(tài) SQL通過 if 標(biāo)簽拼接條件比 ORM 自動(dòng)生成的查詢可控得多也方便直接針對(duì)慢查詢做優(yōu)化。MySQL 則承擔(dān)了穩(wěn)定可靠的數(shù)據(jù)存儲(chǔ)。關(guān)于版本5.7 和 8.x 都可以跑這套系統(tǒng)但從驅(qū)動(dòng)和服務(wù)端兩個(gè)角度我更推薦 8.x。如果必須用 5.7要注意 JDBC 驅(qū)動(dòng)用com.mysql.cj.jdbc.Driver而不是已經(jīng)過時(shí)的com.mysql.jdbc.Driver否則會(huì)有告警甚至連接失敗。2.4 工程目錄怎么組織項(xiàng)目整體分為前端frontend和后端backend兩個(gè)目錄結(jié)構(gòu)如下backend ├── src/main/java/com/xxx/missing │ ├── controller # REST接口層 │ ├── service # 業(yè)務(wù)邏輯層 │ ├── mapper # MyBatis數(shù)據(jù)訪問接口 │ ├── entity # 實(shí)體類 │ ├── dto # 接收參數(shù)的傳輸對(duì)象 │ ├── vo # 返回給前端的數(shù)據(jù)對(duì)象 │ ├── config # 配置類跨域、攔截器、WebMvc │ ├── common # 統(tǒng)一返回結(jié)構(gòu)、異常處理、工具類 │ └── enums # 狀態(tài)枚舉 └── src/main/resources ├── mapper/*.xml # MyBatis SQL映射文件 ├── application.yml └── db/init.sql # 初始化腳本 frontend ├── src │ ├── api # 接口請(qǐng)求封裝 │ ├── router # 路由配置 │ ├── stores # 狀態(tài)管理 │ ├── views # 頁(yè)面組件 │ ├── components # 公共組件 │ └── utils # 請(qǐng)求工具、常量等 └── package.json這種結(jié)構(gòu)最大的好處是職責(zé)清晰前端頁(yè)面不直接拼接口地址統(tǒng)一走api模塊后端每個(gè)層只做自己該做的事。改 SQL 不用動(dòng) Java 代碼改頁(yè)面不用動(dòng)接口分工明確。3. 數(shù)據(jù)庫(kù)建模把人、事、線、審四類數(shù)據(jù)組織起來數(shù)據(jù)庫(kù)設(shè)計(jì)決定了系統(tǒng)能走多遠(yuǎn)。這個(gè)項(xiàng)目里我用的表不多但每張表的關(guān)系和字段都有講究。3.1 核心表清單與設(shè)計(jì)思路先看核心表表名用途核心字段關(guān)系sys_user系統(tǒng)用戶id, username, password, role角色字段區(qū)分審核員/管理員sys_operation_log操作審計(jì)日志user_id, action, target_id, create_time所有重要操作寫日志missing_person走失人員檔案id, name, gender, age, id_card_no, photo_path, feature_desc一比多關(guān)聯(lián)案件missing_case走失案件記錄id, person_id, status, missing_time, missing_address, reporter_id狀態(tài)機(jī)核心表notice公告內(nèi)容id, case_id, title, content, publish_time, status案件與公告一對(duì)一clue線索上報(bào)id, case_id, reporter_name, reporter_phone, content, status案件一對(duì)多線索這里最核心的一條關(guān)系鏈?zhǔn)莔issing_person人對(duì)應(yīng)missing_case案件一個(gè)案件發(fā)布一條notice公告一個(gè)案件收集多條clue線索。人和案件分開是因?yàn)橐粋€(gè)人可能多次走失但每次走失都是獨(dú)立案件不能把多次信息混在一起。3.2 關(guān)鍵表字段細(xì)節(jié)missing_person表里有幾個(gè)字段要特別設(shè)計(jì)id_card_no身份證號(hào)必須脫敏存儲(chǔ)前端展示時(shí)只顯示前六位和后四位中間用星號(hào)代替。真正要做精確比對(duì)時(shí)可以在后端用加密后的值進(jìn)行匹配。photo_path照片不要存二進(jìn)制到數(shù)據(jù)庫(kù)而是把圖片文件存到服務(wù)器目錄或?qū)ο蟠鎯?chǔ)數(shù)據(jù)庫(kù)里只保留相對(duì)路徑。這樣數(shù)據(jù)庫(kù)體積可控加載也更快。feature_desc體貌特征、身著衣物描述建議用text類型但要注意檢索時(shí)不要直接LIKE全表掃最好配合全文索引或分詞處理。missing_case表的狀態(tài)字段是系統(tǒng)最關(guān)鍵的枚舉值DRAFT草稿- PENDING待審核- PUBLISHED已發(fā)布- FOUND已找到- ARCHIVED已歸檔駁回場(chǎng)景下PENDING可以回退到DRAFT并記錄駁回原因。這個(gè)狀態(tài)流轉(zhuǎn)我會(huì)在后端部分詳細(xì)講。建表時(shí)有一句很實(shí)用的提示所有業(yè)務(wù)表的邏輯刪除字段deleted和審計(jì)字段create_time、update_time一定要加上即使現(xiàn)在覺得用不上將來做數(shù)據(jù)回溯和權(quán)限審計(jì)時(shí)都會(huì)需要。示例建表 SQL 大致這樣CREATE TABLE missing_case ( id BIGINT PRIMARY KEY AUTO_INCREMENT, person_id BIGINT NOT NULL COMMENT 關(guān)聯(lián)人員檔案ID, status VARCHAR(20) NOT NULL DEFAULT PENDING COMMENT 案件狀態(tài), missing_time DATETIME NOT NULL COMMENT 走失時(shí)間, missing_address VARCHAR(255) NOT NULL COMMENT 走失地點(diǎn), reporter_id BIGINT NOT NULL COMMENT 登記人用戶ID, deleted TINYINT NOT NULL DEFAULT 0, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_status_time (status, missing_time), KEY idx_person (person_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT走失案件表;注意KEY idx_status_time (status, missing_time)這個(gè)聯(lián)合索引它直接服務(wù)于后臺(tái)按狀態(tài) 按時(shí)間排序的列表查詢。3.3 索引與查詢性能別讓模糊搜索拖垮庫(kù)這類系統(tǒng)最常見的查詢是列表頁(yè)的多條件篩選 分頁(yè)。最容易忽略的問題是模糊搜索對(duì)索引的破壞LIKE 關(guān)鍵字%前綴匹配可以走索引LIKE %關(guān)鍵字%任意位置匹配無法走普通索引會(huì)導(dǎo)致全表掃描。如果必須做任意位置匹配方案有兩個(gè)數(shù)據(jù)量小時(shí)直接用LIKE配合覆蓋索引也還能接受數(shù)據(jù)量大時(shí)給相關(guān)字段建全文索引MySQL 5.7 以上的ngram全文解析器支持中文分詞適合搜姓名和描述。分頁(yè)也有講究。數(shù)據(jù)量小的時(shí)候用LIMIT offset, size沒問題數(shù)據(jù)量大了以后深分頁(yè)會(huì)越來越慢因?yàn)樗亚懊娴臄?shù)據(jù)全部掃一遍。優(yōu)化方案是改為主鍵游標(biāo)分頁(yè)WHERE id 上一頁(yè)最后一條的id ORDER BY id LIMIT size。對(duì)于這套系統(tǒng)前期用普通LIMIT即可但要留出優(yōu)化的擴(kuò)展空間。3.4 歸檔與數(shù)據(jù)留存當(dāng)案件狀態(tài)變?yōu)镕OUND已找到時(shí)公告不能直接物理刪除否則線索和審計(jì)記錄就失去了關(guān)聯(lián)對(duì)象。正確做法是狀態(tài)改為ARCHIVED公告在列表頁(yè)不再展示但數(shù)據(jù)依然保留。我在實(shí)際項(xiàng)目中遇到過人找到了公告還掛在首頁(yè)的尷尬事故就是沒做好狀態(tài)切換導(dǎo)致的所以狀態(tài)機(jī)一定要在數(shù)據(jù)庫(kù)層和業(yè)務(wù)層同時(shí)做約束。4. 后端落地接口設(shè)計(jì)、動(dòng)態(tài)查詢與狀態(tài)機(jī)后端是整個(gè)系統(tǒng)的中樞我挑幾個(gè)最核心的落地點(diǎn)來講。4.1 REST 接口怎么設(shè)計(jì)接口路徑按資源劃分建議如下方法路徑功能POST/api/cases登記走失案件GET/api/cases分頁(yè)查詢案件列表GET/api/cases/{id}案件詳情PUT/api/cases/{id}/status狀態(tài)流轉(zhuǎn)審核/發(fā)布/歸檔GET/api/notices公開公告列表無需登錄POST/api/clues提交線索PUT/api/clues/{id}/status處理線索GET/api/stats/summary數(shù)據(jù)統(tǒng)計(jì)摘要公開接口和受保護(hù)接口要嚴(yán)格區(qū)分。訪客可以看公告列表和提交線索但不能操作后臺(tái)接口。這塊用攔截器做統(tǒng)一校驗(yàn)就行不用每個(gè)方法都重復(fù)寫權(quán)限判斷。4.2 動(dòng)態(tài)查詢MyBatis XML 的核心寫法案件列表頁(yè)的篩選條件是典型的動(dòng)態(tài) SQL 場(chǎng)景。Mapper 接口定義ListCaseVO selectCasePage(Param(query) CaseQueryDTO query, Param(offset) int offset, Param(size) int size);對(duì)應(yīng) XML 的寫法select idselectCasePage resultTypecom.xxx.missing.vo.CaseVO SELECT c.id, p.name, p.gender, c.status, c.missing_time, c.missing_address FROM missing_case c LEFT JOIN missing_person p ON c.person_id p.id where c.deleted 0 if testquery.name ! null and query.name ! AND p.name LIKE CONCAT(%, #{query.name}, %) /if if testquery.status ! null and query.status ! AND c.status #{query.status} /if if testquery.startTime ! null AND c.missing_time gt; #{query.startTime} /if if testquery.endTime ! null AND c.missing_time lt; #{query.endTime} /if /where ORDER BY c.missing_time DESC LIMIT #{offset}, #{size} /select這里有兩個(gè)細(xì)節(jié)值得說第一where標(biāo)簽會(huì)自動(dòng)處理第一個(gè)條件前面的AND避免 SQL 拼接出錯(cuò)第二時(shí)間范圍查詢用起始時(shí)間和結(jié)束時(shí)間兩個(gè)參數(shù)比單獨(dú)傳一個(gè)字符串更安全防止注入。和在 XML 里必須轉(zhuǎn)義成gt;和lt;這個(gè)很多人第一次寫都會(huì)踩坑。同時(shí)還得配一個(gè)selectCasePageCount查詢總數(shù)用于前端分頁(yè)組件。這里建議把條件抽成公共 SQL 片段用sql標(biāo)簽引用避免兩條 SQL 的篩選條件不一致導(dǎo)致列表數(shù)量和總數(shù)對(duì)不上的問題。4.3 狀態(tài)機(jī)的實(shí)現(xiàn)方式狀態(tài)機(jī)不能只靠 if-else。我在代碼里用枚舉統(tǒng)一管理public enum CaseStatus { DRAFT(草稿), PENDING(待審核), PUBLISHED(已發(fā)布), FOUND(已找到), ARCHIVED(已歸檔); private final String desc; }然后定義一張流轉(zhuǎn)表用 Map 或者 狀態(tài)流轉(zhuǎn)配置類來約束允許的路徑DRAFT - PENDING PENDING - PUBLISHED / PENDING - DRAFT駁回 PUBLISHED - FOUND FOUND - ARCHIVED寫入操作時(shí)先判斷當(dāng)前狀態(tài)是否允許流轉(zhuǎn)到目標(biāo)狀態(tài)不允許就直接拋業(yè)務(wù)異常。這個(gè)做法在真實(shí)項(xiàng)目里非常有用它能防止審核員跳過審核直接把草稿改成已發(fā)布這種邏輯漏洞。4.4 統(tǒng)一返回與全局異常接口返回值不要各寫各的統(tǒng)一用一個(gè)結(jié)構(gòu)public class ResultT { private int code; // 0 成功其他為錯(cuò)誤碼 private String msg; private T data; }配合RestControllerAdvice做全局異常處理業(yè)務(wù)異常統(tǒng)一返回錯(cuò)誤碼參數(shù)校驗(yàn)失敗返回字段錯(cuò)誤信息未捕獲異常返回系統(tǒng)繁忙之類的中性提示避免把異常堆棧直接暴露給前端。這個(gè)規(guī)范在聯(lián)調(diào)和排障時(shí)能省大量時(shí)間。5. 前端交互公告擴(kuò)散頁(yè)與管理后臺(tái)的實(shí)現(xiàn)細(xì)節(jié)前端這塊我按訪客看到的公開頁(yè)面和內(nèi)部人員使用的管理頁(yè)面兩條線來講因?yàn)樗鼈兊捏w驗(yàn)?zāi)繕?biāo)和實(shí)現(xiàn)重點(diǎn)完全不同。5.1 路由與狀態(tài)管理前端的路由建議分成兩塊公開區(qū)首頁(yè)公告列表、公告詳情、線索提交、走失登記管理區(qū)案件審核、公告管理、線索處理、數(shù)據(jù)統(tǒng)計(jì)、用戶管理。管理區(qū)的路由統(tǒng)一掛在一個(gè)需要登錄的父路由下配合路由守衛(wèi)做登錄態(tài)校驗(yàn)。狀態(tài)管理用 Vuex 或 Pinia 存用戶信息和權(quán)限標(biāo)記頁(yè)面里根據(jù)角色展示或隱藏對(duì)應(yīng)的操作按鈕。如果管理后臺(tái)的按鈕權(quán)限比較多不要自己一遍遍寫v-if判斷角色封裝一個(gè)v-permission指令會(huì)更省事指令內(nèi)部通過狀態(tài)管理里的權(quán)限列表判斷是否渲染元素。5.2 公告詳情頁(yè)照片、描述和線索提交公開公告詳情頁(yè)有幾個(gè)交互細(xì)節(jié)值得注意照片展示不要一張張平鋪用圖片畫廊組件支持縮放和左右切換。走失人員的照片清晰度通常不高畫廊模式比單圖體驗(yàn)好很多。體貌特征、衣著描述的區(qū)域要突出甚至可以做成獨(dú)立的關(guān)鍵信息卡片放在頁(yè)面頂部而不是藏在長(zhǎng)篇文本里。線索提交表單需要做防抖和防重復(fù)提交。用戶連續(xù)點(diǎn)擊提交按鈕接口會(huì)被請(qǐng)求多次處理方式是在提交后立刻把按鈕置為 loading 狀態(tài)同時(shí)接口側(cè)做冪等控制同一手機(jī)號(hào)對(duì)同一案件短時(shí)間內(nèi)只能提交一次。線索表單示例大致長(zhǎng)這樣el-form refclueFormRef :modelclueForm :rulesclueRules el-form-item label姓名 propreporterName el-input v-modelclueForm.reporterName maxlength30 / /el-form-item el-form-item label聯(lián)系電話 propreporterPhone el-input v-modelclueForm.reporterPhone maxlength11 / /el-form-item el-form-item label線索內(nèi)容 propcontent el-input typetextarea :rows4 v-modelclueForm.content maxlength500 show-word-limit / /el-form-item el-button typeprimary :loadingsubmitting clicksubmitClue提交線索/el-button /el-form這里有個(gè)小的用戶體驗(yàn)技巧提交成功之后不要讓用戶馬上看到提交成功就完事最好在頁(yè)面里展示一句我們將盡快核實(shí)并與你聯(lián)系并隱藏重復(fù)提交的入口這樣既保護(hù)線人信息也避免騷擾。5.3 管理后臺(tái)表格、批量操作與審核隊(duì)列管理后臺(tái)最核心的頁(yè)面就是案件審核列表。用 el-table 加多條件搜索表單狀態(tài)用 el-tag 展示顏色待審核是橙色已發(fā)布是綠色已找到是藍(lán)色已歸檔是灰色。批量操作要格外小心。批量發(fā)布和批量駁回看起來方便但一旦操作失誤影響的是大量案件。我的建議是批量操作只開放狀態(tài)批量駁回并通知登記人這種低風(fēng)險(xiǎn)動(dòng)作高風(fēng)險(xiǎn)動(dòng)作比如批量歸檔要加二次確認(rèn)彈窗并且記錄操作人信息。審核詳情頁(yè)建議用抽屜組件而不是新開頁(yè)面這樣審核員可以在列表和詳情之間快速切換。抽屜里要展示案件時(shí)間線登記時(shí)間、提交時(shí)間、審核時(shí)間、發(fā)布時(shí)間、線索處理時(shí)間全部串成一條縱向時(shí)間線審核員只看一眼就能判斷這單目前卡在哪一步。5.4 圖片加載與列表性能公告列表頁(yè)可能包含大量圖片直接一次性加載會(huì)非???。兩個(gè)處理手段很實(shí)用圖片懶加載和虛擬滾動(dòng)。懶加載可以用現(xiàn)成的指令庫(kù)或者自己寫一個(gè)IntersectionObserver監(jiān)聽圖片進(jìn)入視口后再設(shè)置src。列表項(xiàng)里的圖片建議用統(tǒng)一的縮略圖版本原圖等點(diǎn)擊詳情時(shí)再加載。如果列表超過幾百條表格區(qū)域建議用支持虛擬滾動(dòng)的組件只渲染可視區(qū)域的行不然 DOM 節(jié)點(diǎn)太多頁(yè)面會(huì)掉幀。5.5 聯(lián)調(diào)階段的跨域和 Token 處理前端開發(fā)環(huán)境和后端聯(lián)調(diào)時(shí)最大的問題是跨域。本地開發(fā)時(shí)不建議用瀏覽器插件解決而是通過前端的 devServer 代理// vite.config.ts 或 vue.config.js server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }這樣前端代碼里的請(qǐng)求路徑都寫/api/...開發(fā)環(huán)境由代理轉(zhuǎn)發(fā)生產(chǎn)環(huán)境由 Nginx 轉(zhuǎn)發(fā)前端代碼本身不用區(qū)分環(huán)境。Token 失效的處理也很關(guān)鍵。在 axios 攔截器里統(tǒng)一做請(qǐng)求發(fā)起前從狀態(tài)管理取 token 并加到請(qǐng)求頭響應(yīng)返回 401 時(shí)清理本地登錄態(tài)并跳轉(zhuǎn)到登錄頁(yè)。不要每個(gè)接口單獨(dú)判斷否則代碼會(huì)非常冗余。6. 部署、安全與排坑本機(jī)能跑只是第一步源碼項(xiàng)目在本地跑通很容易真正有價(jià)值的是能部署到生產(chǎn)環(huán)境并且經(jīng)得住一些基本的攻擊和管理場(chǎng)景。這一部分把環(huán)境準(zhǔn)備、生產(chǎn)部署、安全加固和實(shí)際踩過的坑一次說清。6.1 本地環(huán)境準(zhǔn)備要點(diǎn)后端JDK 版本要和 SpringBoot 版本匹配。SpringBoot 2.7.x 用 JDK 8 或 11SpringBoot 3.x 用 JDK 17。很多人啟動(dòng)報(bào)錯(cuò)先懷疑代碼實(shí)際先檢查 JDK 和 Maven 依賴版本。建庫(kù)執(zhí)行db/init.sql注意字符集設(shè)置建議utf8mb4因?yàn)樗芡暾С种形暮?emoji避免中文亂碼。數(shù)據(jù)庫(kù)驅(qū)動(dòng)MySQL 8.x 用com.mysql.cj.jdbc.Driver。前端安裝依賴時(shí)注意 Node 版本老項(xiàng)目用 Node 16新項(xiàng)目用 Node 18/20。如果依賴裝不上優(yōu)先檢查鏡像源配置和 lock 文件。6.2 生產(chǎn)部署方案生產(chǎn)環(huán)境最樸素的方案是前端打包后由 Nginx 托管后端 jar 包交給 systemd 管理MySQL 定時(shí)備份。前端打包npm run build產(chǎn)物是dist目錄把它放到 Nginx 的靜態(tài)目錄下然后配置/api反向代理到后端服務(wù)server { listen 80; server_name your-domain.com; root /var/www/missing-system/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 前端路由支持 history 模式 location / { try_files $uri $uri/ /index.html; } }后端用 systemd 守護(hù)進(jìn)程簡(jiǎn)單可靠崩潰自動(dòng)重啟[Unit] DescriptionMissing System Backend Afternetwork.target [Service] Userdeploy ExecStart/usr/bin/java -jar /var/www/missing-system/backend.jar Restartalways [Install] WantedBymulti-user.target數(shù)據(jù)庫(kù)備份別忽略寫個(gè)定時(shí)任務(wù)每天凌晨備份0 3 * * * mysqldump -u backup_user -ppassword missing_db /backup/missing_$(date \%F).sql6.3 安全加固的常見坑第一坑接口越權(quán)。列表接口如果直接暴露主鍵 ID攻擊者把 ID 換成別人的就可以查看他人案件。解決辦法是查詢時(shí)永遠(yuǎn)附帶當(dāng)前用戶的權(quán)限范圍管理員看全部審核員只看分配給他的或者待審的。第二坑圖片上傳漏洞。上傳接口不要只校驗(yàn) Content-Type因?yàn)榭梢詡卧?。要校?yàn)文件擴(kuò)展名、文件頭魔數(shù)并且把圖片存儲(chǔ)目錄設(shè)置為不可執(zhí)行腳本文件名用隨機(jī)生成的 UUID避免路徑穿越。第三坑XSS 攻擊。公告內(nèi)容如果支持富文本必須過濾script標(biāo)簽以及各類事件屬性否則別人提交的內(nèi)容可能在你管理臺(tái)執(zhí)行腳本。建議前端用白名單過濾后端再做一次校驗(yàn)。6.4 典型的排坑復(fù)盤這里列出我在實(shí)際運(yùn)行這套系統(tǒng)時(shí)遇到的四個(gè)高概率問題每個(gè)都有對(duì)應(yīng)的解決思路問題現(xiàn)象根因解決方式SpringBoot 啟動(dòng)失敗提示數(shù)據(jù)源配置異常版本太高如升級(jí)到 3.x 后部分配置項(xiàng)名稱變化檢查spring.datasource配置或回退到源碼匹配的 SpringBoot 版本連接 MySQL 報(bào)時(shí)區(qū)錯(cuò)誤MySQL 8.x 默認(rèn)時(shí)區(qū)與 JDBC 不一致在 JDBC URL 加serverTimezoneAsia/Shanghai和useSSLfalse查出的實(shí)體屬性全是 null下劃線列名沒有映射到駝峰屬性配置map-underscore-to-camel-case: true或顯式寫 resultMap分頁(yè)總數(shù)和列表數(shù)據(jù)不一致查詢列表和查詢總數(shù)的條件不一致用sql抽取公共篩選條件兩處統(tǒng)一引用最后一個(gè)問題尤其隱蔽因?yàn)樗粫?huì)報(bào)錯(cuò)只是數(shù)據(jù)量上去之后統(tǒng)計(jì)數(shù)字越來越奇怪。我建議在寫完列表接口后馬上對(duì)比一下兩個(gè) SQL 在同樣參數(shù)下是否返回相同口徑的結(jié)果?;氐竭@套系統(tǒng)本身我在實(shí)際開發(fā)中還有一個(gè)深刻的體會(huì)源碼給你的是基礎(chǔ)但真正能體現(xiàn)水平的是你對(duì)它做的安全加固和業(yè)務(wù)適配。比如給案件增加區(qū)域維度、給線索增加核實(shí)狀態(tài)、給統(tǒng)計(jì)模塊增加時(shí)段對(duì)比這些擴(kuò)展都是在現(xiàn)有表結(jié)構(gòu)上做的加法不會(huì)傷筋動(dòng)骨。數(shù)據(jù)庫(kù)字段設(shè)計(jì)得規(guī)范業(yè)務(wù)擴(kuò)展時(shí)就會(huì)很輕松。這套系統(tǒng)最大的價(jià)值就是給你一個(gè)結(jié)構(gòu)清晰的起點(diǎn)讓你能把精力花在真正應(yīng)該花的地方。