公司售后服務(wù)管理系統(tǒng)實(shí)戰(zhàn):SSM框架與MySQL部署全解析)
簡(jiǎn)介這是一份保險(xiǎn)公司售后服務(wù)管理系統(tǒng)完整源碼包面向保險(xiǎn)行業(yè)軟件開(kāi)發(fā)者、畢業(yè)設(shè)計(jì)學(xué)生及系統(tǒng)運(yùn)維人員解決保單管理、理賠處理、客戶(hù)服務(wù)、核保風(fēng)控、財(cái)務(wù)結(jié)算等售后流程一體化管理問(wèn)題。資源共909個(gè)文件包含184個(gè)html頁(yè)面、152個(gè)class編譯類(lèi)、134個(gè)css樣式、122個(gè)js腳本、120個(gè)java源碼及121個(gè)png圖片等壓縮包整體約1.61MB覆蓋前端展示、后端邏輯與靜態(tài)資源目錄結(jié)構(gòu)清晰。從內(nèi)容預(yù)覽可見(jiàn)SaleListAdminController、GoodsAdminController等控制類(lèi)能幫助讀者快速理解訂單、商品、用戶(hù)等模塊的后臺(tái)管理實(shí)現(xiàn)。已有54人學(xué)習(xí)下載適合用于保險(xiǎn)業(yè)務(wù)系統(tǒng)課程設(shè)計(jì)、代碼學(xué)習(xí)或二次開(kāi)發(fā)參考可借此了解保險(xiǎn)售后業(yè)務(wù)流程的代碼實(shí)現(xiàn)與系統(tǒng)架構(gòu)。1. 保險(xiǎn)公司售后服務(wù)管理系統(tǒng)從保單簽出之后的爛攤子說(shuō)起一張保單簽出去業(yè)務(wù)員的活兒只算干了一半。真正的麻煩從售后開(kāi)始客戶(hù)出險(xiǎn)要報(bào)案、到期要續(xù)保、對(duì)理賠不滿(mǎn)意要投訴、新產(chǎn)品上線要回訪。這些事要是還靠 Excel 登記業(yè)務(wù)員離職帶走一張表整個(gè)服務(wù)鏈條就斷了。保險(xiǎn)公司售后服務(wù)管理系統(tǒng)就是接管簽單之后的這一整套動(dòng)作登記服務(wù)工單、跟蹤理賠進(jìn)度、自動(dòng)生成續(xù)保提醒、記錄回訪和滿(mǎn)意度。這類(lèi)壓縮包里通常是一套 Java Web 項(xiàng)目SSM 框架加 MySQL 數(shù)據(jù)庫(kù)前端頁(yè)面直接跑在 Tomcat 上。適合中小保險(xiǎn)分支機(jī)構(gòu)和代理網(wǎng)點(diǎn)內(nèi)部用也常被當(dāng)作畢業(yè)設(shè)計(jì)和實(shí)訓(xùn)項(xiàng)目的底子。重點(diǎn)別急著點(diǎn)啟動(dòng)按鈕先把這個(gè)系統(tǒng)的數(shù)據(jù)模型讀明白再談怎么跑起來(lái)。2. 拆開(kāi)壓縮包看數(shù)據(jù)設(shè)計(jì)保單、理賠、回訪、續(xù)保四組核心表拿到壓縮包之后第一件事不是往 IDE 里導(dǎo)而是先看目錄結(jié)構(gòu)和數(shù)據(jù)庫(kù)腳本。這類(lèi)項(xiàng)目一般包含幾樣固定的東西一個(gè) Eclipse 或 IDEA 工程目錄、一個(gè) .sql 數(shù)據(jù)庫(kù)腳本、一份說(shuō)明文檔。腳本是整套系統(tǒng)的靈魂我習(xí)慣先讀表結(jié)構(gòu)再讀代碼因?yàn)槭酆笙到y(tǒng)的業(yè)務(wù)邏輯幾乎全部體現(xiàn)在表設(shè)計(jì)上狀態(tài)怎么流轉(zhuǎn)、費(fèi)用怎么記錄、提醒怎么觸發(fā)。表結(jié)構(gòu)沒(méi)吃透后面改代碼等于在黑匣子里瞎試。這四張表建議按順序去看先主表后子表把依賴(lài)關(guān)系一層層拉出來(lái)。2.1 售后工單主表以保單號(hào)為中心把每件事串起來(lái)售后模塊幾乎都以工單為中心。一次理賠報(bào)案、一通投訴電話、一次主動(dòng)回訪都生成一條工單記錄掛到對(duì)應(yīng)的保單下面。主表設(shè)計(jì)大致是這樣CREATE TABLE aftersale_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 工單號(hào), policy_no VARCHAR(32) NOT NULL COMMENT 保單號(hào), customer_id INT NOT NULL COMMENT 客戶(hù)ID, service_type TINYINT NOT NULL COMMENT 1理賠 2投訴 3咨詢(xún) 4續(xù)保 5回訪, status TINYINT NOT NULL DEFAULT 1 COMMENT 1待處理 2處理中 3已完成 4已關(guān)閉, assignee VARCHAR(32) COMMENT 處理人, apply_time DATETIME COMMENT 申請(qǐng)時(shí)間, finish_time DATETIME COMMENT 完成時(shí)間, remark VARCHAR(500) COMMENT 備注, KEY idx_policy (policy_no), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT售后工單主表;建表的關(guān)鍵點(diǎn)全在索引上。售后頁(yè)面里最常見(jiàn)的查詢(xún)就是某個(gè)保單下的全部服務(wù)記錄和當(dāng)前待處理的工單列表所以 idx_policy 和 idx_status 這兩個(gè)索引必須有否則數(shù)據(jù)量過(guò)萬(wàn)之后每次按保單查都做全表掃描頁(yè)面響應(yīng)會(huì)肉眼可見(jiàn)地變慢。status 字段用 TINYINT 存數(shù)字而不是字符串代碼里比對(duì)方便也省空間但必須在 COMMENT 里寫(xiě)清楚每個(gè)數(shù)字的含義不然半年后沒(méi)人看得懂 status3 到底代表什么。另外注意 order_no 和 policy_no 是業(yè)務(wù)編號(hào)客戶(hù)看得見(jiàn)、要用來(lái)對(duì)賬所以單獨(dú)設(shè)列id 只是給程序內(nèi)部關(guān)聯(lián)用的。很多畢業(yè)設(shè)計(jì)把業(yè)務(wù)編號(hào)直接當(dāng)主鍵一旦要改單號(hào)規(guī)則就得動(dòng)主鍵非常被動(dòng)。工單與保單是一對(duì)多的關(guān)系一張保單可以有多條工單反過(guò)來(lái)不行這個(gè)方向不能搞反。2.2 理賠子表狀態(tài)字段就是理賠流程的進(jìn)度條理賠是整個(gè)售后系統(tǒng)里最重的模塊因?yàn)樯婕敖痤~核定和多角色流轉(zhuǎn)。它的子表設(shè)計(jì)通常長(zhǎng)這樣CREATE TABLE claim_info ( id INT PRIMARY KEY AUTO_INCREMENT, claim_no VARCHAR(32) NOT NULL COMMENT 報(bào)案號(hào), order_id INT NOT NULL COMMENT 關(guān)聯(lián)工單ID, policy_no VARCHAR(32) NOT NULL COMMENT 保單號(hào), accident_time VARCHAR(32) COMMENT 出險(xiǎn)時(shí)間, report_time DATETIME COMMENT 報(bào)案時(shí)間, claim_amount DECIMAL(12,2) COMMENT 申請(qǐng)金額, approved_amount DECIMAL(12,2) COMMENT 核定金額, status TINYINT NOT NULL DEFAULT 1 COMMENT 1已報(bào)案 2查勘中 3核賠中 4待支付 5已結(jié)案 6已拒賠, adjuster VARCHAR(32) COMMENT 查勘員, UNIQUE KEY uk_claim_no (claim_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT理賠信息表;這里最值得琢磨的是狀態(tài)字段。理賠狀態(tài)是一條固定鏈報(bào)案 → 查勘 → 核賠 → 待支付 → 結(jié)案拒賠是核賠階段的分支分支。代碼里會(huì)按這個(gè)數(shù)字狀態(tài)寫(xiě)判斷頁(yè)面上顯示查勘中就用狀態(tài)值對(duì)應(yīng)文案。所以改狀態(tài)枚舉是整個(gè)項(xiàng)目里風(fēng)險(xiǎn)最高的事新增狀態(tài)可以但改舊狀態(tài)的數(shù)字含義會(huì)導(dǎo)致歷史數(shù)據(jù)全部亂套——之前歸檔的報(bào)案記錄狀態(tài)含義全變了統(tǒng)計(jì)報(bào)表也會(huì)跟著錯(cuò)。出險(xiǎn)時(shí)間我用 VARCHAR(32) 而不是 DATETIME這是故意為之。實(shí)際業(yè)務(wù)里出險(xiǎn)時(shí)間經(jīng)常是客戶(hù)口頭描述格式五花八門(mén)2025年3月5號(hào)和2025-03-05都有查勘員錄入時(shí)不會(huì)規(guī)規(guī)矩矩按日期格式填。字符串反而不會(huì)在錄入階段報(bào)錯(cuò)展示時(shí)原樣輸出統(tǒng)計(jì)時(shí)再做格式歸一。這個(gè)細(xì)節(jié)是踩過(guò)坑才改的初期用 DATETIME前端一傳2025/03/05數(shù)據(jù)庫(kù)直接報(bào)錯(cuò)。金額字段用 DECIMAL(12,2) 而不是 DOUBLE因?yàn)楸kU(xiǎn)場(chǎng)景對(duì)金額精度有硬要求。DOUBLE 的浮點(diǎn)誤差在累計(jì)對(duì)賬時(shí)會(huì)暴露0.1 加 0.2 算出 0.30000000000000004 這種事在理賠金額統(tǒng)計(jì)里沒(méi)法接受。DECIMAL 是精確類(lèi)型只是存儲(chǔ)上稍微多占幾個(gè)字節(jié)對(duì)這套系統(tǒng)來(lái)說(shuō)完全可以忽略。2.3 回訪與滿(mǎn)意度表讓服務(wù)過(guò)變成服務(wù)得好理賠結(jié)案之后要回訪保單到期之前要回訪新產(chǎn)品上線也要回訪?;卦L記錄表是統(tǒng)計(jì)滿(mǎn)意度的數(shù)據(jù)來(lái)源CREATE TABLE visit_record ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, policy_no VARCHAR(32) NOT NULL, customer_id INT NOT NULL, visit_type TINYINT COMMENT 1理賠回訪 2續(xù)?;卦L 3滿(mǎn)意度調(diào)查, content VARCHAR(500) COMMENT 回訪內(nèi)容, satisfaction TINYINT COMMENT 1不滿(mǎn) 2一般 3滿(mǎn)意 4很滿(mǎn)意, next_visit_date DATE COMMENT 下次回訪日期, operator VARCHAR(32) COMMENT 回訪人, create_time DATETIME COMMENT 回訪時(shí)間 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT回訪記錄表;satisfaction 字段是整個(gè)售后服務(wù)質(zhì)量評(píng)估的量化依據(jù)。這個(gè)季度客戶(hù)滿(mǎn)意度 96%這類(lèi)指標(biāo)SQL 寫(xiě)法就是COUNT(satisfaction 3) / COUNT(*)。沒(méi)有這個(gè)字段售后做得好不好就只能是業(yè)務(wù)員嘴上的自我評(píng)價(jià)領(lǐng)導(dǎo)看報(bào)表也無(wú)從下手。所以這張表的第一個(gè)價(jià)值不是記錄是量化。很多人做這張表會(huì)漏掉 operator回訪人字段?;卦L業(yè)務(wù)的本質(zhì)是任務(wù)分配客服每天打開(kāi)系統(tǒng)看到今天要回訪 15 個(gè)客戶(hù)這 15 個(gè)必須能按人過(guò)濾。沒(méi)有 operator 就只能查全量再自己挨個(gè)認(rèn)領(lǐng)分配機(jī)制就塌了。這個(gè)字段不是可選項(xiàng)是業(yè)務(wù)能否流轉(zhuǎn)起來(lái)的前提。同理 next_visit_date 用于生成明天待回訪列表也別刪。2.4 續(xù)保提醒日期算法是這里唯一的技術(shù)門(mén)檻續(xù)保提醒是售后系統(tǒng)里離錢(qián)最近的功能邏輯本身不復(fù)雜保單到期前 N 天生成提醒任務(wù)。真正要小心的是日期邊界。常見(jiàn)的實(shí)現(xiàn)是一條 SQL 掃描即將到期的保單SELECT * FROM policy_info WHERE expire_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 30 DAY) AND renewal_status 0;這里三個(gè)坑。第一CURDATE() 依賴(lài)數(shù)據(jù)庫(kù)服務(wù)器時(shí)區(qū)MySQL 的 time_zone 要統(tǒng)一設(shè)成 Asia/Shanghai否則跨時(shí)區(qū)部署時(shí)提醒會(huì)提前或延后一天。第二expire_date 如果是 DATETIME 類(lèi)型查詢(xún)到期日當(dāng)天的保單要小心BETWEEN 默認(rèn)從 00:00:00 開(kāi)始到期日當(dāng)天的保單如果存的是帶時(shí)間的值邊界判斷會(huì)漏數(shù)據(jù)。第三renewal_status 字段是防重復(fù)提醒的開(kāi)關(guān)生成提醒后必須置 1否則第二天任務(wù)又跑一遍客戶(hù)被短信連續(xù)轟炸投訴電話直接打到客服主管那里。policy_info 表一般長(zhǎng)什么樣核心字段不外乎 policy_no、customer_id、product_type、start_date、expire_date、renewal_status。續(xù)保提醒的任務(wù)就是盯住 expire_date 這一個(gè)字段所以這張表上 expire_date 必須建索引定時(shí)任務(wù)天天掃這張表沒(méi)有索引就是一場(chǎng)災(zāi)難。實(shí)際項(xiàng)目里我建議把提前 30 天這個(gè)數(shù)字從代碼里抽出來(lái)。業(yè)務(wù)上很可能下個(gè)月就改成提前 45 天寫(xiě)死在 SQL 里每次都要改代碼重新部署。怎么改成可配置第 6 章會(huì)專(zhuān)門(mén)講。3. 把系統(tǒng)跑起來(lái)解壓、導(dǎo)庫(kù)、改配置、啟動(dòng)四步全流程表結(jié)構(gòu)過(guò)了一遍這一章落地。跑通這類(lèi)項(xiàng)目的完整流程可以壓縮成一句話解壓工程導(dǎo)入 IDE、導(dǎo)入數(shù)據(jù)庫(kù)腳本、改數(shù)據(jù)源配置文件、部署到 Tomcat 啟動(dòng)。每步都有固定的坑按下面順序來(lái)半小時(shí)內(nèi)能見(jiàn)到登錄頁(yè)。3.1 環(huán)境組合怎么選JDK 8、Tomcat 8.5、MySQL 5.7 為什么最穩(wěn)先別急著動(dòng)手把環(huán)境對(duì)齊再開(kāi)始不然后面全是環(huán)境報(bào)錯(cuò)。這類(lèi)壓縮包大多是近幾年的實(shí)訓(xùn)項(xiàng)目技術(shù)棧以 SSM 為主。這個(gè)年代的項(xiàng)目對(duì)運(yùn)行環(huán)境非常挑剔不是越新越好。軟件推薦版本用新版版本的代價(jià)JDK1.8JDK 17 運(yùn)行舊框架會(huì)報(bào) javax/jakarta 包缺失改起來(lái)傷筋動(dòng)骨Tomcat8.5Tomcat 11 要求 Jakarta EE舊項(xiàng)目基本跑不了MySQL5.7MySQL 8 改了認(rèn)證插件舊版 JDBC 驅(qū)動(dòng)直接連不上IDEIntelliJ IDEA / Eclipse導(dǎo)入方式不同本質(zhì)都是識(shí)別 Maven 或普通 Web 工程這個(gè)組合是這類(lèi)項(xiàng)目性?xún)r(jià)比最高的選擇。JDK 8 是 SSM 框架的舒適區(qū)Spring 4.x/5.x、MyBatis 3.x 全是官方支持版本Tomcat 8.5 兼容 JDK 8內(nèi)嵌 Servlet 3.1正好對(duì)上 SpringMVC 的老配置方式MySQL 5.7 與項(xiàng)目自帶的 .sql 腳本語(yǔ)法兼容度最高。有的壓縮包會(huì)附帶 README 寫(xiě)明 JDK 版本要求先看那個(gè)文件按它的要求來(lái)。如果沒(méi)寫(xiě)就按上面這套組合九成能跑起來(lái)。剩下那一成跑不起來(lái)的問(wèn)題大概率不在版本在配置。3.2 導(dǎo)入數(shù)據(jù)庫(kù)兩種方式與編碼陷阱數(shù)據(jù)庫(kù)腳本一般叫 db_insurance.sql 或 insurance_db.sql在壓縮包根目錄或 sql 目錄下。先用文本編輯器打開(kāi)看文件頭部判斷里面有沒(méi)有 CREATE DATABASE 語(yǔ)句。有直接導(dǎo)入mysql -uroot -p db_insurance.sql沒(méi)有的話先建庫(kù)再導(dǎo)入mysql -uroot -p CREATE DATABASE insurance DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE insurance; SOURCE /path/to/db_insurance.sql;SOURCE 是 mysql 客戶(hù)端的內(nèi)置命令后面跟本地絕對(duì)路徑。Windows 下路徑里的反斜杠會(huì)被當(dāng)成轉(zhuǎn)義符要把 C:\projects\db.sql 寫(xiě)成 C:/projects/db.sql。這個(gè)細(xì)節(jié)不處理好會(huì)報(bào)一個(gè)莫名其妙的語(yǔ)法錯(cuò)誤很容易讓人誤以為是 SQL 文件本身有問(wèn)題。導(dǎo)入完成后立刻驗(yàn)證確認(rèn)數(shù)據(jù)真實(shí)進(jìn)庫(kù)了USE insurance; SHOW TABLES; SELECT COUNT(*) FROM aftersale_order;如果 aftersale_order 這個(gè)表名不存在說(shuō)明腳本里表名不一樣用 SHOW TABLES 看一下實(shí)際表名再繼續(xù)。編碼坑集中在腳本文件的字符集上。很多腳本是從 Windows 環(huán)境導(dǎo)出的文件本身是 GBK 編碼庫(kù)是 UTF-8導(dǎo)入后表注釋和字典表數(shù)據(jù)全成亂碼。遇到這種情況把 SQL 文件用編輯器另存為 UTF-8 編碼再導(dǎo)一次。或者導(dǎo)入時(shí)顯式指定字符集mysql -uroot -p --default-character-setutf8mb4 insurance db_insurance.sql提示判斷亂碼的簡(jiǎn)單辦法是看字典表里中文內(nèi)容而不是表名表名全是 ASCII亂碼不會(huì)體現(xiàn)在表名上。3.3 改數(shù)據(jù)源配置jdbc.properties 里的四個(gè)必改項(xiàng)數(shù)據(jù)庫(kù)導(dǎo)好之后把工程導(dǎo)入 IDE找到數(shù)據(jù)源配置文件。SSM 工程里它通常叫 jdbc.properties放在 src/main/resources 下。這個(gè)文件決定了 Java 代碼能不能連上剛才建好的庫(kù)至少要改四行jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/insurance?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.passwordyour_password前兩行通常模板里寫(xiě)死了要對(duì)照你的實(shí)際情況核一遍。jdbc.url 里有幾個(gè)關(guān)鍵參數(shù)useUnicodetrue 和 characterEncodingutf8 保證中文正常讀寫(xiě)useSSLfalse 省掉本地開(kāi)發(fā)的 SSL 握手開(kāi)銷(xiāo)serverTimezoneAsia/Shanghai 是必須加的舊版 mysql-connector-java 5.1.x 連 MySQL 5.7 不配時(shí)區(qū)參數(shù)會(huì)直接拋異常啟動(dòng)就失敗。用戶(hù)名密碼改成你本地 MySQL 的實(shí)際賬號(hào)。注意別用 root 空密碼去試很多新版 MySQL 默認(rèn) root 用 auth_socket 插件命令行能進(jìn)但 JDBC 連不上。給應(yīng)用建一個(gè)獨(dú)立賬號(hào)最省事也方便后續(xù)回收權(quán)限CREATE USER insurance_applocalhost IDENTIFIED BY YourPass123; GRANT ALL PRIVILEGES ON insurance.* TO insurance_applocalhost; FLUSH PRIVILEGES;改完配置文件后確認(rèn) IDE 打包時(shí)用的是你改過(guò)的這份。用 Maven 管理的工程target/classes 下會(huì)殘留舊的 .propertiesIDEA 不會(huì)每次都幫你清運(yùn)行前先 mvn clean 一次。不然你改了半天跑的還是舊配置這種問(wèn)題最容易讓人懷疑人生。3.4 部署到 Tomcat 并啟動(dòng)war 包還是 IDE 直接跑配置改完進(jìn)入啟動(dòng)階段。兩種常見(jiàn)選擇IDE 里集成 Tomcat 插件直接跑適合調(diào)試打 war 包丟到獨(dú)立 Tomcat 的 webapps 目錄適合模擬線上環(huán)境。平時(shí)調(diào)試用第一種做完功能后用第二種驗(yàn)證一遍。獨(dú)立 Tomcat 的方式更直觀也好排查問(wèn)題cd /path/to/project mvn clean package -DskipTests cp target/insurance.war /path/to/tomcat/webapps/ cd /path/to/tomcat/bin ./startup.sh tail -f /path/to/tomcat/logs/catalina.outTomcat 啟動(dòng)后會(huì)自動(dòng)解壓 war 包到 webapps/insurance/ 目錄??吹?catalina.out 里出現(xiàn) Deployment of web application archive insurance.war has finished 才算部署完成。這份日志是整個(gè)啟動(dòng)過(guò)程最權(quán)威的記錄任何啟動(dòng)失敗都會(huì)在這里留下堆棧別去看控制臺(tái)控制臺(tái)的信息經(jīng)常不完整。瀏覽器訪問(wèn) http://localhost:8080/insurance/ 或 README 里寫(xiě)的入口路徑看到登錄頁(yè)就通了。這里有個(gè)新手最容易誤判的地方打開(kāi) http://localhost:8080 看到 Tomcat 默認(rèn)首頁(yè)就以為部署成功了。那是 ROOT 應(yīng)用的頁(yè)面跟你的項(xiàng)目沒(méi)有關(guān)系你的應(yīng)用要帶 /insurance 前綴訪問(wèn)。如果啟動(dòng)后頁(yè)面 404先別慌看 Tomcat 日志里有沒(méi)有 Context 初始化失敗的提示再確認(rèn)訪問(wèn)路徑。第 5 章專(zhuān)門(mén)講這類(lèi)問(wèn)題。4. 上線前必調(diào)的參數(shù)連接池、定時(shí)任務(wù)、上傳路徑與日志本地跑通只是第一步真要讓業(yè)務(wù)員日常用起來(lái)必須把教學(xué)項(xiàng)目里的默認(rèn)參數(shù)過(guò)一遍。默認(rèn)值是為三個(gè)人玩設(shè)計(jì)的不是為三十個(gè)人干活設(shè)計(jì)的。這一章講四個(gè)最關(guān)鍵的調(diào)整點(diǎn)連接池怎么才能不垮、定時(shí)任務(wù)怎么不漏單、上傳怎么不報(bào)錯(cuò)、日志怎么留得下來(lái)。每一條都是上線后最容易出問(wèn)題的位置。4.1 連接池參數(shù)maxActive 和 validationQuery 決定系統(tǒng)能撐多久舊 SSM 項(xiàng)目大多是 DBCP 或 C3P0 連接池配置在 spring-datasource.xml 里。默認(rèn)配置往往只有基礎(chǔ)四項(xiàng)上線后第一個(gè)瓶頸就在這里。bean iddataSource classorg.apache.commons.dbcp.BasicDataSource destroy-methodclose property namedriverClassName valuecom.mysql.jdbc.Driver / property nameurl value${jdbc.url} / property nameusername value${jdbc.username} / property namepassword value${jdbc.password} / property namemaxActive value50 / property namemaxIdle value10 / property namemaxWait value60000 / property nameinitialSize value5 / property namevalidationQuery valueSELECT 1 / property nametestOnBorrow valuetrue / /bean參數(shù)含義拆開(kāi)說(shuō)。maxActive50 是連接池能同時(shí)提供的最大連接數(shù)。30 個(gè)業(yè)務(wù)員同時(shí)在線操作加上定時(shí)任務(wù)偶爾占連接50 是穩(wěn)妥值如果公司網(wǎng)點(diǎn)規(guī)模更大按峰值在線人數(shù)除以 2估算。maxIdle10 是空閑時(shí)保留的最大連接數(shù)防止頻繁重建連接。maxWait60000 表示排隊(duì)拿連接最長(zhǎng)等 60 秒超時(shí)直接拋異常。這里不要設(shè)成 -1否則連接耗盡時(shí)請(qǐng)求會(huì)無(wú)限掛起頁(yè)面卡死看不出原因重啟 Tomcat 才好。initialSize5 是啟動(dòng)時(shí)預(yù)建的連接數(shù)應(yīng)用剛起來(lái)就能扛住第一波請(qǐng)求不用等業(yè)務(wù)來(lái)了現(xiàn)場(chǎng)建連。這個(gè)參數(shù)教學(xué)項(xiàng)目里基本沒(méi)有加上之后啟動(dòng)速度會(huì)慢一兩秒但值得。最關(guān)鍵的是最后兩行validationQuery 和 testOnBorrow。MySQL 服務(wù)端的 wait_timeout 默認(rèn) 8 小時(shí)連接閑置超過(guò)這個(gè)時(shí)間會(huì)被服務(wù)端斷開(kāi)但連接池不知道下次取到這條死連接一執(zhí)行就報(bào) Communications link failure。testOnBorrowtrue 讓每次從池子拿連接前先執(zhí)行一次 SELECT 1連接是死的就舍棄重建。這是治每天早上第一波操作必卡的后悔藥任何跑了一段時(shí)間就報(bào)連接異常的項(xiàng)目先檢查這兩行在不在。4.2 續(xù)保提醒定時(shí)任務(wù)cron 表達(dá)式與漏單補(bǔ)償續(xù)保提醒是系統(tǒng)的招牌功能但默認(rèn)實(shí)現(xiàn)往往把日期寫(xiě)死任務(wù)也沒(méi)有補(bǔ)償機(jī)制。Spring 的定時(shí)任務(wù)需要兩處配置注解驅(qū)動(dòng)和任務(wù)類(lèi)。!-- 啟用 Spring 注解式定時(shí)任務(wù) -- task:annotation-driven /任務(wù)類(lèi)里由 Scheduled 驅(qū)動(dòng)Component public class RenewalTask { Scheduled(cron 0 0 9 * * ?) public void doRemind() { ListPolicyInfo list policyMapper.findExpiringWithin(30); for (PolicyInfo p : list) { if (!remindLogMapper.existsByPolicyId(p.getId())) { remindLogMapper.insert(new RemindLog(p.getId(), new Date())); // 此處調(diào)用短信服務(wù)商接口發(fā)送續(xù)保提醒 } } } }cron 表達(dá)式是秒 分 時(shí) 日 月 星期六位0 0 9 * * ? 表示每天 9 點(diǎn)整執(zhí)行。注意 Spring 的 cron 和 Linux crontab 不一樣Spring 從秒開(kāi)始一共六位Linux 是五位從分開(kāi)始。把 Linux 的表達(dá)式填進(jìn)來(lái)啟動(dòng)會(huì)直接報(bào) IllegalStateException。定時(shí)任務(wù)選在早上 9 點(diǎn)是因?yàn)檫@個(gè)時(shí)間業(yè)務(wù)員剛上班客戶(hù)接電話意愿也高夜里跑純屬浪費(fèi)短信費(fèi)。邏輯上的漏單問(wèn)題如果 9 點(diǎn)整系統(tǒng)正在重啟或者數(shù)據(jù)庫(kù)恰好鎖表這一輪任務(wù)就漏了。漏掉的續(xù)保提醒意味著客戶(hù)可能悄悄去別家續(xù)保。我一般在任務(wù)類(lèi)里加啟動(dòng)補(bǔ)償PostConstruct public void compensate() { doRemind(); // 應(yīng)用啟動(dòng)時(shí)補(bǔ)跑一次保證當(dāng)天不會(huì)漏 logger.info(啟動(dòng)補(bǔ)償執(zhí)行完成); }PostConstruct 在 Spring 容器初始化完成后執(zhí)行補(bǔ)跑一次把今天該提醒的單子補(bǔ)上。配合 remindLog 表的去重邏輯重復(fù)跑也不會(huì)發(fā)重復(fù)短信。注意補(bǔ)跑和定時(shí)任務(wù)有并發(fā)風(fēng)險(xiǎn)方法上加 synchronized 兜底或者用任務(wù)框架的鎖。這個(gè)細(xì)節(jié)能避免重復(fù)提醒。4.3 文件上傳與日志兩個(gè)最容易被忽略的配置理賠報(bào)案要傳現(xiàn)場(chǎng)照片上傳功能是售后流程里繞不開(kāi)的環(huán)節(jié)。Spring MVC 需要 multipart 解析器才能處理文件上傳配置在 spring-mvc.xmlbean idmultipartResolver classorg.springframework.web.multipart.commons.CommonsMultipartResolver property namemaxUploadSize value52428800 / property namemaxInMemorySize value1048576 / property namedefaultEncoding valueUTF-8 / /beanmaxUploadSize52428800 即 50MB。按現(xiàn)場(chǎng)照片每張 2-3MB、一次傳 5-8 張來(lái)算50MB 留了余量。maxInMemorySize 表示 1MB 以?xún)?nèi)的小文件直接放內(nèi)存超過(guò)的落磁盤(pán)避免大文件占用堆內(nèi)存。另外頁(yè)面的 form 表單必須加 enctypemultipart/form-data 屬性漏掉這個(gè)接口永遠(yuǎn)拿不到文件前端還不報(bào)錯(cuò)只是后端 MultipartFile 參數(shù)一直是 null。上傳目錄也要改。很多項(xiàng)目把上傳路徑寫(xiě)死在代碼里換臺(tái)機(jī)器就報(bào)目錄不存在。改成配置項(xiàng)并自動(dòng)建目錄Value(${upload.path}) private String uploadPath; PostConstruct public void init() { File dir new File(uploadPath); if (!dir.exists()) { dir.mkdirs(); } }日志配置同樣值得過(guò)一遍。這類(lèi)項(xiàng)目的 log4j.properties 默認(rèn)輸出到控制臺(tái)上線后沒(méi)有文件日志出了問(wèn)題無(wú)從查起log4j.rootLoggerINFO, stdout, file log4j.appender.fileorg.apache.log4j.DailyRollingFileAppender log4j.appender.file.File/var/log/insurance/insurance.log log4j.appender.file.DatePattern.yyyy-MM-dd log4j.appender.file.layoutorg.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern%d{yyyy-MM-dd HH:mm:ss} %p %c:%L - %m%n log4j.logger.com.insurance.mapperDEBUGDailyRollingFileAppender 按天滾動(dòng)日志不會(huì)無(wú)限變大。pattern 里的 %c:%L 輸出類(lèi)名和行號(hào)排查問(wèn)題直接定位到代碼位置。com.insurance.mapper 包打 DEBUG 級(jí)別MyBatis 會(huì)打印每條 SQL 語(yǔ)句和參數(shù)這是排查數(shù)據(jù)問(wèn)題時(shí)最趁手的工具上線穩(wěn)定后把這行改成 INFO否則查詢(xún)量大時(shí)日志文件刷得飛快。注意日志目錄 /var/log/insurance/ 要提前創(chuàng)建并確認(rèn) Tomcat 進(jìn)程有寫(xiě)權(quán)限否則啟動(dòng)時(shí)報(bào)錯(cuò)但進(jìn)程不退出很容易被忽略。5. 運(yùn)行中的常見(jiàn)問(wèn)題排查五條真實(shí)踩坑記錄這一章直接給結(jié)論。跑起來(lái)之后遇到的各種報(bào)錯(cuò)里以下五類(lèi)占了這類(lèi)項(xiàng)目九成的問(wèn)題。每一條都是實(shí)際踩過(guò)的按現(xiàn)象、原因、解決的順序?qū)懩眠^(guò)來(lái)對(duì)照處理就行。5.1 啟動(dòng)報(bào) ClassNotFoundExceptionlib 目錄沒(méi)把依賴(lài)帶進(jìn)來(lái)現(xiàn)象Tomcat 啟動(dòng)成功但一訪問(wèn)頁(yè)面就報(bào) java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet或者 NoClassDefFoundError。原因SSM 項(xiàng)目的依賴(lài) jar 沒(méi)有進(jìn)入 WEB-INF/lib。用 IDEA 跑的時(shí)候是 IDE 自己提供了 classpath打包部署后沒(méi)人管依賴(lài)了。常見(jiàn)根源是 IDEA 的 Artifact 配置里沒(méi)把庫(kù)文件放進(jìn) war 包或 pom.xml 里依賴(lài)的 scope 寫(xiě)成了 provided編譯時(shí)有部署時(shí)被排除。解決IDEA 里打開(kāi) Project Structure → Artifacts選中項(xiàng)目在 Available Elements 里找到所有 Library右鍵選擇 Put into /WEB-INF/lib重新 Build 后確認(rèn) war 解壓目錄下 WEB-INF/lib 里有 spring-webmvc.jar。Maven 項(xiàng)目則檢查 pom.xml把依賴(lài)的 scope 改成 compile 或直接刪掉 provided。改完重建再部署。這類(lèi)問(wèn)題排查起來(lái)最費(fèi)時(shí)間的就是定位方向先看 lib 目錄而不是改代碼。5.2 頁(yè)面中文亂碼三處字符集必須一致現(xiàn)象登錄進(jìn)去客戶(hù)姓名、保單類(lèi)型等中文內(nèi)容顯示成問(wèn)號(hào)或亂碼符號(hào)。原因請(qǐng)求、頁(yè)面、數(shù)據(jù)庫(kù)三處字符集不一致。典型組合是 JSP 頁(yè)面用 UTF-8數(shù)據(jù)庫(kù)表是 latin1Tomcat 接收請(qǐng)求用 ISO-8859-1任何一處沒(méi)對(duì)齊中文就廢。解決按順序統(tǒng)一。第一步JSP 頁(yè)面頂部加% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8 %第二步數(shù)據(jù)庫(kù)表統(tǒng)一改 utf8mb4ALTER TABLE aftersale_order CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;第三步Tomcat 的 conf/server.xml 里給 HTTP Connector 加 URIEncodingUTF-8。改完重啟 Tomcat亂碼能解決九成。還有一成在數(shù)據(jù)庫(kù)連接串上jdbc.url 里 characterEncodingutf8 必須帶這個(gè)前面第 3 章已經(jīng)講過(guò)。三處全對(duì)齊還亂碼就查頁(yè)面表單提交時(shí)有沒(méi)有指定 accept-charset以及 MySQL 連接器版本是否過(guò)舊。5.3 登錄頁(yè) 404Tomcat 部署路徑缺了一個(gè)斜杠現(xiàn)象應(yīng)用能啟動(dòng)訪問(wèn) http://localhost:8080/insurance 報(bào) 404但訪問(wèn) http://localhost:8080/insurance/ 就正常。原因Tomcat 對(duì)不帶斜杠的路徑會(huì)嘗試找默認(rèn)歡迎頁(yè)。這類(lèi)項(xiàng)目通常沒(méi)有 index.jsp目錄列表又被關(guān)閉所以根路徑就 404 了。這不是部署失敗是缺一個(gè)斜杠。解決兩個(gè)方向任選。要么在 web.xml 里配歡迎頁(yè)把登錄頁(yè)加進(jìn)去welcome-file-list welcome-filelogin.jsp/welcome-file /welcome-file-list要么推廣統(tǒng)一入口讓業(yè)務(wù)員收藏完整路徑 http://localhost:8080/insurance/login.jsp。這個(gè)問(wèn)題的迷惑性在于讓人誤以為部署失敗反復(fù)重新部署浪費(fèi)時(shí)間。先確認(rèn)帶斜杠的路徑能不能通能通就只是路徑問(wèn)題不是部署問(wèn)題。5.4 跑了一周后連不上數(shù)據(jù)庫(kù)MySQL 8 小時(shí)斷連現(xiàn)象系統(tǒng)剛上線頭幾天正常一周后業(yè)務(wù)員反饋早上操作很慢或者直接報(bào) Connection is not available, request timed out。重啟 Tomcat 又好了第二天又犯。原因經(jīng)典的 MySQL wait_timeout 斷連。MySQL 默認(rèn)把空閑超過(guò) 8 小時(shí)的連接斷掉連接池不知道繼續(xù)把失效連接分給應(yīng)用。連接池變成黑匣子導(dǎo)致的必踩坑不是代碼邏輯問(wèn)題。解決三管齊下。第一MySQL 端調(diào)大 wait_timeoutSET GLOBAL wait_timeout28800重啟 MySQL 后要寫(xiě)進(jìn) my.cnf 的 [mysqld] 段才持久化。第二連接池配置加 validationQuerySELECT 1 和 testOnBorrowtrue這是根治手段拿連接前先探活。第三DBCP 連接池還可以加 minEvictableIdleTimeMillis60000讓空閑連接每分鐘被回收重建。前兩步做掉這個(gè)故障不會(huì)再出。這個(gè)問(wèn)題的隱蔽性在于啟動(dòng)后一切正常問(wèn)題的觸發(fā)條件是時(shí)間不是操作所以很難在測(cè)試階段暴露。5.5 端口被占用8080 被別的程序搶了現(xiàn)象Tomcat 啟動(dòng)日志報(bào) Address already in use: JVM_Bind 或 Failed to initialize end point associated with ProtocolHandler http-bio-8080。原因8080 端口被占用??赡苁橇硪粋€(gè) Tomcat 實(shí)例、Nginx、某個(gè)開(kāi)發(fā)工具的本地服務(wù)或者上次 Tomcat 沒(méi)有完全退出。Windows 下最常見(jiàn)的是 Oracle 和 WebLogic 也愛(ài)用 8080。解決先查誰(shuí)占了端口# Linux / macOS lsof -i :8080 # Windows netstat -ano | findstr 8080拿到占用進(jìn)程的 PID確認(rèn)是無(wú)關(guān)服務(wù)就殺掉。如果端口被公司其他系統(tǒng)占用不能動(dòng)就改 Tomcat 端口在 conf/server.xml 里Connector port8081 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 /改成 8081 后記得同步檢查項(xiàng)目代碼里有沒(méi)有硬編碼的 8080 地址。有些項(xiàng)目把回調(diào)地址寫(xiě)死在配置里頁(yè)面能打開(kāi)但接口請(qǐng)求全打到舊端口表現(xiàn)為頁(yè)面加載一半就報(bào)錯(cuò)。全局搜一遍 8080 字符串把所有硬編碼一起改掉。6. 二次開(kāi)發(fā)的兩個(gè)實(shí)用技巧讓通用項(xiàng)目變成能用的系統(tǒng)跑通、調(diào)完參數(shù)系統(tǒng)已經(jīng)能接真實(shí)業(yè)務(wù)了。但還有兩個(gè)值得動(dòng)手的小改造能把通用模板變成貼合自己業(yè)務(wù)流程的系統(tǒng)。6.1 用 Spring AOP 給售后操作加操作日志售后流程最怕扯皮客戶(hù)說(shuō)我上周就提交了報(bào)案業(yè)務(wù)員說(shuō)沒(méi)收到系統(tǒng)里查不到記錄。與其指望每個(gè)人自覺(jué)點(diǎn)日志按鈕不如在架構(gòu)層面統(tǒng)一記錄。用 Spring AOP 做一個(gè)切面在寫(xiě)操作的方法上自動(dòng)落日志Aspect Component public class OperationLogAspect { Around(annotation(operationLog)) public Object record(ProceedingJoinPoint pjp, OperationLog operationLog) throws Throwable { Object result pjp.proceed(); OperationLogEntity log new OperationLogEntity(); log.setAction(operationLog.value()); log.setOperator(SecurityUtils.getCurrentUser()); log.setOperateTime(new Date()); log.setDetail(pjp.getSignature().toShortString()); operationLogMapper.insert(log); return result; } }這里只攔截標(biāo)了 OperationLog 注解的方法不影響正常讀操作的性能。理賠狀態(tài)變更、工單轉(zhuǎn)派、續(xù)保提醒生成這幾類(lèi)核心寫(xiě)操作各標(biāo)一個(gè)注解就能自動(dòng)留痕。配合一個(gè)簡(jiǎn)單的查詢(xún)頁(yè)面所有操作事后可回溯。這個(gè)改造的成本非常低但對(duì)售后管理的價(jià)值很高出了糾紛直接查日志表。6.2 把續(xù)保提醒提前天數(shù)改成配置項(xiàng)第 2 章提過(guò)續(xù)保提醒的提前 30 天如果寫(xiě)死在 SQL 里每次調(diào)整都要?jiǎng)哟a。改成配置化很簡(jiǎn)單建一張 reminder_config 表把提前天數(shù)、提醒文案、是否啟用放進(jìn)去定時(shí)任務(wù)每次執(zhí)行前先讀配置再按配置值掃描保單。業(yè)務(wù)上要改成 45 天直接在配置頁(yè)改數(shù)字不用再找開(kāi)發(fā)。這類(lèi)業(yè)務(wù)上會(huì)變的數(shù)字統(tǒng)一放進(jìn)配置是這類(lèi)項(xiàng)目里性?xún)r(jià)比最高的改造。改造完成后的驗(yàn)證方法很關(guān)鍵手動(dòng)把一條保單的到期日改成今天加提前天數(shù)減一天的范圍然后手動(dòng)觸發(fā)一次任務(wù)看提醒記錄表有沒(méi)有生成記錄短信接口有沒(méi)有收到調(diào)用。驗(yàn)證通過(guò)再放開(kāi)定時(shí)任務(wù)避免上線即翻車(chē)。我以前做過(guò)一個(gè)項(xiàng)目沒(méi)做這個(gè)驗(yàn)證直接上定時(shí)任務(wù)結(jié)果 cron 表達(dá)式因?yàn)閷?xiě)成了五位沒(méi)生效續(xù)保提醒靜默失敗了一個(gè)月等發(fā)現(xiàn)時(shí)這個(gè)月的續(xù)保數(shù)據(jù)已經(jīng)沒(méi)法追回了。從那以后任何定時(shí)任務(wù)上線之前我都先手動(dòng)觸發(fā)一次并檢查日志。這個(gè)習(xí)慣幫你挽回的損失可能遠(yuǎn)不止一個(gè)月。希望這個(gè)系統(tǒng)能真正跑在你的業(yè)務(wù)里也希望你少走我當(dāng)年走過(guò)的彎路。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取