產(chǎn)權(quán)管理系統(tǒng):申報(bào)審核與付費(fèi)下載全流程解析)
簡(jiǎn)介基于SSM框架與MySQL開(kāi)發(fā)的Java Web知識(shí)產(chǎn)權(quán)管理系統(tǒng)面向Java Web學(xué)習(xí)、課程設(shè)計(jì)與畢業(yè)設(shè)計(jì)場(chǎng)景。系統(tǒng)采用前臺(tái)后臺(tái)模式前臺(tái)支持用戶注冊(cè)登錄、公告查看、知識(shí)產(chǎn)權(quán)搜索、免費(fèi)資料下載以及付費(fèi)下載可自定義費(fèi)用和租借時(shí)長(zhǎng)后臺(tái)區(qū)分管理員、員工、注冊(cè)用戶三類(lèi)角色管理員可進(jìn)行密碼修改、員工管理、用戶管理、公告管理、資料下載管理及申報(bào)結(jié)果公布員工負(fù)責(zé)申報(bào)審核與失敗反饋?zhàn)?cè)用戶可在線申報(bào)、查看結(jié)果并管理個(gè)人資料。資源包共1071個(gè)文件約32.36MB主要包含JSP頁(yè)面、Java源碼、class文件、jar依賴(lài)庫(kù)以及JS/CSS前端資源、GIF/PNG圖片、SQL數(shù)據(jù)庫(kù)腳本等目錄結(jié)構(gòu)完整可直接導(dǎo)入Eclipse配合MySQL運(yùn)行。已有78人學(xué)習(xí)參考適合需要完整前后臺(tái)項(xiàng)目源碼、數(shù)據(jù)庫(kù)腳本與業(yè)務(wù)流程參照的開(kāi)發(fā)者。1. 知識(shí)產(chǎn)權(quán)管理系統(tǒng)把申報(bào)、審核和付費(fèi)下載從臺(tái)賬里搬出來(lái)做知識(shí)產(chǎn)權(quán)申報(bào)和資料分發(fā)的小團(tuán)隊(duì)通常靠Excel加網(wǎng)盤(pán)的組合拳申報(bào)人交一份Word審核人用Excel登記結(jié)果資料下載掛個(gè)網(wǎng)盤(pán)鏈接過(guò)期了再手動(dòng)續(xù)。這套基于SSM框架的知識(shí)產(chǎn)權(quán)管理系統(tǒng)把「申報(bào)、審核、公布、付費(fèi)下載」串成了一條完整業(yè)務(wù)鏈。前臺(tái)用戶注冊(cè)登錄后可以搜索知識(shí)產(chǎn)權(quán)、下載免費(fèi)資料、付費(fèi)租借下載權(quán)限后臺(tái)員工負(fù)責(zé)申報(bào)審核和失敗反饋管理員統(tǒng)一管理員工、用戶、公告、申報(bào)結(jié)果公布以及帶編號(hào)、名稱(chēng)、費(fèi)用、圖片、租借時(shí)間的付費(fèi)產(chǎn)權(quán)信息。技術(shù)棧很典型SpringSpringMVCMyBatisJSP頁(yè)面MySQL數(shù)據(jù)庫(kù)Eclipse工程。適合兩類(lèi)人一是有知識(shí)產(chǎn)權(quán)申報(bào)管理真實(shí)需求的機(jī)構(gòu)二是想找完整JavaWeb前后臺(tái)案例來(lái)復(fù)現(xiàn)的開(kāi)發(fā)者。2. 系統(tǒng)架構(gòu)拆解SSMJSP的前后臺(tái)雙入口與關(guān)鍵類(lèi)2.1 前臺(tái)與后臺(tái)的職責(zé)邊界用戶在用什么、員工在管什么這套系統(tǒng)不是前后端分離也不是微服務(wù)而是傳統(tǒng)JSP項(xiàng)目里典型的「一個(gè)工程、多套入口」模式。前臺(tái)頁(yè)面是注冊(cè)用戶看到的部分核心動(dòng)作可以壓縮成三個(gè)詞查、下、報(bào)。查是搜索知識(shí)產(chǎn)權(quán)和查看公告下是免費(fèi)下載和付費(fèi)下載報(bào)是提交新的知識(shí)產(chǎn)權(quán)申報(bào)資料。后臺(tái)則是另一個(gè)世界按角色繼續(xù)劈成兩半——員工登錄進(jìn)去只能處理申報(bào)審核和失敗反饋管理員則能碰員工管理、用戶注冊(cè)管理、公告信息管理、資料下載管理、申報(bào)結(jié)果公布和付費(fèi)產(chǎn)權(quán)信息維護(hù)。角色區(qū)分的實(shí)現(xiàn)方式不復(fù)雜常見(jiàn)做法是登錄成功后把角色標(biāo)識(shí)寫(xiě)進(jìn)SessionJSP頁(yè)面用標(biāo)簽判斷角色來(lái)渲染菜單Controller層的方法入口再檢查一次角色。從項(xiàng)目類(lèi)名來(lái)看Controller拆得相當(dāng)細(xì)YonghuController管用戶登錄下載、YonghuzhuceController管注冊(cè)、YuangongController管員工審核、ChanquanxinxiController管產(chǎn)權(quán)信息。這種按角色加資源拆控制器的方式好處是每個(gè)類(lèi)職責(zé)單一出問(wèn)題時(shí)定位很快代價(jià)是Controller數(shù)量多如果后期加功能類(lèi)會(huì)比較膨脹。這里要提醒一句菜單隱藏絕不是權(quán)限控制。如果只在頁(yè)面上把后臺(tái)入口藏了沒(méi)有在服務(wù)端校驗(yàn)角色用戶直接在瀏覽器里拼一個(gè)后臺(tái)URL照樣能訪問(wèn)員工功能。拿到這套系統(tǒng)后建議先統(tǒng)覽一遍所有Controller的請(qǐng)求映射再?zèng)Q定要不要加攔截器具體做法在第6章會(huì)展開(kāi)。2.2 核心Controller與數(shù)據(jù)流轉(zhuǎn)鏈路從項(xiàng)目里能看出的幾個(gè)主要控制器職責(zé)梳理如下控制器類(lèi)職責(zé)典型請(qǐng)求前綴YonghuController前臺(tái)用戶登錄、個(gè)人信息修改、下載處理/user/**YonghuzhuceController新用戶注冊(cè)、注冊(cè)信息落庫(kù)/registerYuangongController員工申報(bào)審核、失敗反饋、個(gè)人資料/staff/**ChanquanxinxiController產(chǎn)權(quán)信息查詢(xún)、免費(fèi)付費(fèi)下載入口/info/**數(shù)據(jù)處理的大致鏈路是這樣的用戶在前臺(tái)訪問(wèn)列表頁(yè)Controller先把產(chǎn)權(quán)信息從數(shù)據(jù)庫(kù)查出來(lái)經(jīng)過(guò)分頁(yè)和搜索條件過(guò)濾由SpringMVC把數(shù)據(jù)放到Model里再交給JSP渲染。用戶提交申報(bào)時(shí)表單數(shù)據(jù)和上傳文件一起進(jìn)Controller申報(bào)記錄插入數(shù)據(jù)庫(kù)狀態(tài)字段初始化為「待審核」。員工登錄后臺(tái)后YuangongController去查所有待審核記錄審核通過(guò)就把狀態(tài)改成「已通過(guò)」該記錄對(duì)前臺(tái)用戶變?yōu)榭梢?jiàn)審核不通過(guò)就寫(xiě)一條失敗反饋用戶在前臺(tái)「我的申報(bào)結(jié)果」里能看到駁回原因。這條鏈路上的狀態(tài)字段是關(guān)鍵。常見(jiàn)約定是0代表待審核、1代表已通過(guò)、2代表已駁回。搜索功能看似簡(jiǎn)單實(shí)際有幾個(gè)容易忽略的點(diǎn)按編號(hào)搜索時(shí)要用模糊匹配還是精確匹配按名稱(chēng)搜索時(shí)中文編碼能不能正確處理分頁(yè)參數(shù)在翻頁(yè)后會(huì)不會(huì)丟失。這個(gè)項(xiàng)目里搜索入口不止前臺(tái)有員工后臺(tái)也有一份兩者應(yīng)該走同一個(gè)Service方法避免兩套SQL邏輯后來(lái)出現(xiàn)不一致。2.3 工具類(lèi)文件db.class與Info.class的定位以及那批asp文件怎么處理項(xiàng)目里有幾個(gè)不以Controller命名的類(lèi)分別是db.class和Info.class。在SSM工程里數(shù)據(jù)庫(kù)訪問(wèn)一般由MyBatis管理DataSource在Spring配置文件里統(tǒng)一配置db.class極可能是早期JDBC風(fēng)格的連接封裝被保留下來(lái)給某些獨(dú)立模塊或歷史代碼使用。Info.class則是一個(gè)典型的通用消息包裝類(lèi)作用是統(tǒng)一Controller返回給頁(yè)面的提示信息。它內(nèi)部結(jié)構(gòu)大致是public class Info { private Integer code; // 0為成功1為失敗 private String message; // 提示內(nèi)容 public Info(Integer code, String message) { this.code code; this.message message; } }邏輯說(shuō)明這類(lèi)工具類(lèi)本身不處理業(yè)務(wù)只是把「成或敗、成功提示、失敗原因」打包成一個(gè)對(duì)象傳到JSP。頁(yè)面拿到之后用EL表達(dá)式讀code和message決定彈窗還是跳轉(zhuǎn)。參數(shù)說(shuō)明code的取值約定全項(xiàng)目要統(tǒng)一不然前端判斷會(huì)亂message最好用中文業(yè)務(wù)提示方便用戶直接看懂。再看那幾個(gè)支付相關(guān)文件alipay_md5.asp、alipay_function.asp、alipay_notify.asp、alipay_service.asp。在JavaWeb項(xiàng)目里看到asp后綴文件很多人會(huì)以為下錯(cuò)了資源其實(shí)這并不罕見(jiàn)——早期的支付接口常以ASP文件形式分發(fā)開(kāi)發(fā)者在遷移系統(tǒng)時(shí)把這份參考文件也順帶放進(jìn)了工程目錄。Tomcat不會(huì)執(zhí)行asp所以這些文件對(duì)項(xiàng)目運(yùn)行沒(méi)有任何影響保留當(dāng)文檔看就好真正要接支付時(shí)應(yīng)該用官方Java SDK重寫(xiě)而不是試圖在Tomcat里跑asp。這一點(diǎn)在第5章避坑部分還會(huì)單獨(dú)講。3. 數(shù)據(jù)庫(kù)設(shè)計(jì)申報(bào)、審核、下載這三件事怎么建模3.1 核心表結(jié)構(gòu)與字段清單從功能反推這套系統(tǒng)至少需要這幾張核心表用戶表、員工表、公告表、產(chǎn)權(quán)信息表、申報(bào)表、下載記錄表。由于項(xiàng)目里Controller名字用的拼音表名和字段名大概率也是拼音風(fēng)格實(shí)際工作時(shí)不要被這個(gè)風(fēng)格嚇到它只是命名習(xí)慣差異。產(chǎn)權(quán)信息表是關(guān)鍵中的關(guān)鍵因?yàn)樗吻芭_(tái)展示、免費(fèi)下載、付費(fèi)租借、管理員維護(hù)四類(lèi)操作字段上必須把編號(hào)、名稱(chēng)、費(fèi)用、圖片、介紹、租借時(shí)間這些信息都接住。CREATE TABLE chanquan_xinxi ( id INT PRIMARY KEY AUTO_INCREMENT, bianhao VARCHAR(50) NOT NULL COMMENT 產(chǎn)權(quán)編號(hào), mingcheng VARCHAR(200) NOT NULL COMMENT 名稱(chēng), feiyong DECIMAL(10,2) DEFAULT 0.00 COMMENT 下載費(fèi)用, tupian VARCHAR(255) DEFAULT COMMENT 展示圖片路徑, jieshao TEXT COMMENT 產(chǎn)權(quán)介紹, zujin_days INT DEFAULT 30 COMMENT 付費(fèi)后租借天數(shù), status TINYINT DEFAULT 0 COMMENT 0未公布 1已公布 );邏輯說(shuō)明這張表把前臺(tái)付費(fèi)下載展示需要的字段全部收納進(jìn)來(lái)。bianhao對(duì)應(yīng)列表頁(yè)顯示的編號(hào)feiyong用DECIMAL存金額避免浮點(diǎn)誤差zujin_days存天數(shù)付費(fèi)成功后系統(tǒng)根據(jù)它計(jì)算到期時(shí)間。參數(shù)說(shuō)明status為0時(shí)前臺(tái)不可見(jiàn)管理員在后臺(tái)維護(hù)完信息后改為1前臺(tái)列表才會(huì)出現(xiàn)這條記錄如果要做下架能力可以把status改成2表示已下架。申報(bào)表也是核心表它要連接用戶和產(chǎn)權(quán)還要承接審核狀態(tài)和反饋信息。字段大致包括user_id記錄申報(bào)人chanquan_id關(guān)聯(lián)前面的產(chǎn)權(quán)信息表apply_docs存申報(bào)文檔路徑status存審核狀態(tài)feedback存失敗原因。這里要注意反饋?zhàn)侄我欢ㄒ蛯徍藭r(shí)間分開(kāi)存不然用戶看到反饋卻不知道是什么時(shí)候給的會(huì)出現(xiàn)體驗(yàn)問(wèn)題。3.2 租借時(shí)間與下載記錄的關(guān)系設(shè)計(jì)付費(fèi)下載這個(gè)模塊最核心的設(shè)計(jì)決策是把「一次購(gòu)買(mǎi)永久下載」改成「一次付費(fèi)租借一段時(shí)間」。如果只設(shè)計(jì)一個(gè)flag字段標(biāo)識(shí)用戶買(mǎi)沒(méi)買(mǎi)過(guò)后續(xù)狀態(tài)會(huì)非常難維護(hù)——用戶買(mǎi)過(guò)之后不能撤銷(xiāo)過(guò)期了也無(wú)法再收費(fèi)。正確做法是在下載記錄表里同時(shí)存開(kāi)始時(shí)間和到期時(shí)間每次用戶發(fā)起下載時(shí)都去判斷當(dāng)前時(shí)間落在哪個(gè)區(qū)間。CREATE TABLE download_log ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, chanquan_id INT NOT NULL, type TINYINT DEFAULT 0 COMMENT 0免費(fèi) 1付費(fèi), download_time DATETIME DEFAULT NULL, expire_time DATETIME DEFAULT NULL );邏輯說(shuō)明type字段區(qū)分免費(fèi)下載和付費(fèi)下載免費(fèi)記錄的expire_time可以為NULL表示不限制過(guò)期付費(fèi)記錄必須寫(xiě)到期時(shí)間。查詢(xún)有效租期的SQL是整個(gè)付費(fèi)下載權(quán)限判斷的核心。參數(shù)說(shuō)明download_time是這次權(quán)限的開(kāi)始時(shí)間expire_time等于download_time加zujin_days天兩個(gè)時(shí)間都應(yīng)由服務(wù)端生成不要讓客戶端傳。實(shí)際查詢(xún)邏輯如下用戶每次點(diǎn)付費(fèi)下載先查這條記錄SELECT * FROM download_log WHERE user_id #{userId} AND chanquan_id #{chanquanId} AND expire_time NOW() AND type 1 ORDER BY id DESC LIMIT 1;邏輯說(shuō)明如果這條SQL返回一條記錄說(shuō)明用戶當(dāng)前處于有效租期內(nèi)直接放行下載如果返回空說(shuō)明沒(méi)有購(gòu)買(mǎi)記錄或者租期已過(guò)期此時(shí)應(yīng)該引導(dǎo)用戶重新支付。參數(shù)說(shuō)明NOW()是MySQL當(dāng)前時(shí)間如果你的項(xiàng)目里服務(wù)器時(shí)間和數(shù)據(jù)庫(kù)時(shí)間不一致租期判斷會(huì)翻車(chē)后面避坑部分會(huì)專(zhuān)門(mén)講這個(gè)問(wèn)題。4. 下載鏈路實(shí)戰(zhàn)免費(fèi)與付費(fèi)兩條路徑的代碼要害4.1 免費(fèi)下載身份校驗(yàn)、文件讀取、日志記錄三步走免費(fèi)下載看似簡(jiǎn)單代碼里也要走完身份校驗(yàn)、文件讀取、日志記錄三步缺任何一步都會(huì)留下隱患。我一般會(huì)在Controller方法開(kāi)頭先取Session里的用戶對(duì)象拿不到就直接重定向到登錄頁(yè)這是防止未登錄用戶繞過(guò)頁(yè)面按鈕直接請(qǐng)求下載地址的關(guān)鍵。GetMapping(/freeDownload) public void freeDownload(Integer infoId, HttpSession session, HttpServletResponse response) throws IOException { Yonghu user (Yonghu) session.getAttribute(loginUser); if (user null) { response.sendRedirect(/login); return; } Chanquan info infoService.getById(infoId); if (info null || info.getStatus() ! 1) { response.sendRedirect(/index); return; } File file new File(info.getSavePath()); String fileName URLEncoder.encode(info.getFileName(), UTF-8) .replaceAll(\\, %20); response.setContentType(application/octet-stream); response.setHeader(Content-Disposition, attachment;filename fileName); downloadService.log(user.getId(), infoId, 0); FileInputStream fis new FileInputStream(file); // 流復(fù)制寫(xiě)入response.getOutputStream() }邏輯說(shuō)明第一步取Session用戶第二步校驗(yàn)產(chǎn)權(quán)信息存在并且狀態(tài)是已公布第三步才是下載響應(yīng)和記錄日志。參數(shù)說(shuō)明infoId是產(chǎn)權(quán)表主鍵type傳0表示免費(fèi)下載fileName經(jīng)過(guò)URLEncoder編碼否則中文文件名會(huì)在瀏覽器里變成亂碼或直接丟失。注意這里下載日志是下載前記錄好處是用戶流量已經(jīng)產(chǎn)生壞處是如果文件讀取失敗會(huì)留下一條不完整記錄實(shí)際項(xiàng)目里可以把寫(xiě)日志放到流復(fù)制成功之后。4.2 付費(fèi)下載有效租期判斷與過(guò)期重新支付付費(fèi)下載的Controller核心邏輯只有一句話先查有效租期有就放行沒(méi)有就跳支付。這句話實(shí)現(xiàn)起來(lái)雖然不長(zhǎng)但最容易出錯(cuò)的地方在于「有效」二字的定義。有效租期必須同時(shí)滿足三個(gè)條件用戶ID匹配、產(chǎn)權(quán)ID匹配、到期時(shí)間晚于當(dāng)前時(shí)間。條件缺一個(gè)都不能放行。GetMapping(/payDownload) public void payDownload(Integer infoId, HttpSession session, HttpServletResponse response) throws IOException { Yonghu user (Yonghu) session.getAttribute(loginUser); if (user null) { response.sendRedirect(/login); return; } DownloadLog log downloadService.findValidRent(user.getId(), infoId); if (log null) { response.sendRedirect(/pay?infoId infoId); return; } downloadService.countDownload(log.getId()); // 寫(xiě)入文件流邏輯同免費(fèi)下載 }邏輯說(shuō)明findValidRent內(nèi)部執(zhí)行的就是第3章那段SQL查不到有效記錄就跳轉(zhuǎn)支付頁(yè)面生成訂單。這里有個(gè)細(xì)節(jié)跳轉(zhuǎn)支付的URL要帶上infoId支付成功后回調(diào)里要靠這個(gè)參數(shù)去定位產(chǎn)權(quán)記錄。參數(shù)說(shuō)明countDownload是記錄用戶在租期內(nèi)總共下載了多少次方便管理員后續(xù)統(tǒng)計(jì)如果不想做這個(gè)統(tǒng)計(jì)這一行可以省掉。租期設(shè)計(jì)上還有一個(gè)容易被忽略的點(diǎn)到期時(shí)長(zhǎng)的計(jì)算應(yīng)該以支付成功時(shí)刻為起點(diǎn)而不是下單時(shí)刻。用戶可能下單后一直沒(méi)付款隔了兩天再付如果以下單時(shí)間計(jì)算租期就會(huì)白扣兩天。常見(jiàn)做法是支付成功回調(diào)落庫(kù)時(shí)取系統(tǒng)當(dāng)前時(shí)間作為download_time再累加zujin_days天作為expire_time。4.3 支付回調(diào)與簽名校驗(yàn)別把「管理員手動(dòng)改狀態(tài)」當(dāng)真方案付費(fèi)下載要真正跑通支付回調(diào)是繞不開(kāi)的。支付平臺(tái)在用戶付款成功后會(huì)往你配置的回調(diào)地址發(fā)一個(gè)異步通知你的代碼需要在通知里做兩件事驗(yàn)簽和落庫(kù)。驗(yàn)簽是為了確認(rèn)這個(gè)請(qǐng)求真的來(lái)自支付平臺(tái)不是用戶偽造的落庫(kù)是把有效租期寫(xiě)入download_log表寫(xiě)完之后用戶才有點(diǎn)付費(fèi)下載的權(quán)限。RequestMapping(/alipayNotify) ResponseBody public String alipayNotify(HttpServletRequest request) { String orderId request.getParameter(out_trade_no); String tradeStatus request.getParameter(trade_status); if (TRADE_SUCCESS.equals(tradeStatus) signVerified(request)) { downloadService.createRentByOrderId(orderId); return success; } return failure; }邏輯說(shuō)明先判斷交易狀態(tài)再驗(yàn)簽驗(yàn)簽通過(guò)后把訂單轉(zhuǎn)換成租期記錄。參數(shù)說(shuō)明out_trade_no是業(yè)務(wù)側(cè)下單時(shí)生成的唯一訂單號(hào)createRentByOrderId會(huì)根據(jù)訂單號(hào)找到對(duì)應(yīng)的用戶和產(chǎn)權(quán)然后插入download_log并計(jì)算expire_time。返回success這個(gè)詞是給支付平臺(tái)看的約定字符串只有返回success它才停止重發(fā)通知返回其他任何內(nèi)容都會(huì)被當(dāng)作處理失敗支付平臺(tái)會(huì)繼續(xù)重試。這里要特別強(qiáng)調(diào)項(xiàng)目zip里自帶的alipay_md5.asp等文件只能作為接口字段的參考文檔。真正在Java項(xiàng)目里接支付是用官方Java SDK發(fā)起下單請(qǐng)求、驗(yàn)簽、接收異步通知而不是把這些asp文件放到Tomcat里跑。如果你只是本地復(fù)現(xiàn)項(xiàng)目也可以先用支付回調(diào)入口加一個(gè)測(cè)試模擬開(kāi)關(guān)跳過(guò)真實(shí)支付流程——但要記得上線前必須關(guān)掉。5. 部署與避坑EclipseTomcatMySQL導(dǎo)入首跑的6條踩坑記錄5.1 項(xiàng)目導(dǎo)入與運(yùn)行環(huán)境準(zhǔn)備拿到zip解壓后建議先用Eclipse導(dǎo)入整個(gè)項(xiàng)目目錄選擇Existing Projects into Workspace而不是新建項(xiàng)目再拷代碼。導(dǎo)入后第一件事是打開(kāi)項(xiàng)目屬性找到Java Compiler把JDK調(diào)整到1.8——這個(gè)項(xiàng)目推測(cè)是在JDK8時(shí)期開(kāi)發(fā)的用太高版本編譯會(huì)報(bào)一些奇怪的類(lèi)庫(kù)兼容問(wèn)題。第二步給項(xiàng)目配置Tomcat運(yùn)行時(shí)把項(xiàng)目加入Tomcat的Server列表里讓Eclipse知道去哪里找Servlet相關(guān)類(lèi)庫(kù)。# 啟動(dòng)前先確認(rèn)端口占用Linux/macOS用 lsof -i:8080 # Windows用 netstat -ano | findstr 8080邏輯說(shuō)明Tomcat默認(rèn)端口是8080如果本機(jī)其他服務(wù)占用了這個(gè)端口Tomcat會(huì)在Eclipse里報(bào)端口被占用或啟動(dòng)超時(shí)。參數(shù)說(shuō)明如果確定要換端口去Tomcat的conf/server.xml里改Connector標(biāo)簽的port屬性但不建議—后續(xù)所有前臺(tái)頁(yè)面的鏈接和回調(diào)地址都跟著變?nèi)菀茁└摹?.2 數(shù)據(jù)庫(kù)初始化與數(shù)據(jù)庫(kù)連接配置數(shù)據(jù)庫(kù)配置在Spring的jdbc配置文件里常見(jiàn)位置是resources目錄下屬性包括數(shù)據(jù)庫(kù)地址、用戶名、密碼和連接驅(qū)動(dòng)。MySQL數(shù)據(jù)庫(kù)名需要和連接URL保持一致字符集必須指定utf8否則頁(yè)面顯示中文會(huì)是一堆問(wèn)號(hào)。jdbc.urljdbc:mysql://localhost:3306/db_ssm?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456邏輯說(shuō)明characterEncodingutf8保證中文寫(xiě)入和讀取不亂碼useSSLfalse是因?yàn)楸緳C(jī)開(kāi)發(fā)環(huán)境一般沒(méi)有配置SSL證書(shū)MySQL新版默認(rèn)會(huì)嘗試動(dòng)用SSL連接并報(bào)警告serverTimezoneAsia/Shanghai則專(zhuān)門(mén)解決MySQL 8的時(shí)區(qū)報(bào)錯(cuò)“The server time zone value ?D1ú±ê×?ê±?? is unrecognized”。參數(shù)說(shuō)明root和123456只是本地開(kāi)發(fā)用的占位示意實(shí)際部署請(qǐng)換成獨(dú)立賬號(hào)并設(shè)強(qiáng)密碼。5.3 常見(jiàn)問(wèn)題與排查記錄坑1解壓后看到src目錄下有4個(gè)asp文件以為下錯(cuò)了項(xiàng)目現(xiàn)象alipay_md5.asp、alipay_function.asp、alipay_notify.asp、alipay_service.asp躺在項(xiàng)目源碼目錄里懷疑是混合代碼工程。原因這是從老ASP站遷移或參考文件拷貝殘留Tomcat不解析asp不影響Java運(yùn)行。解決不用刪除把它們當(dāng)接口字段參考就行真要接支付自己用JavaSDK重寫(xiě)。如果看著礙眼把這四個(gè)文件移出項(xiàng)目源碼目錄也不會(huì)破壞編譯???Eclipse導(dǎo)入后大面積紅xjavax.servlet找不到現(xiàn)象項(xiàng)目樹(shù)里所有JSP文件、Controller文件都報(bào)“The superclass javax.servlet.http.HttpServlet was not found on the Java Build Path”。原因項(xiàng)目沒(méi)有引入Tomcat運(yùn)行時(shí)Eclipse不知道去哪找Servlet和JSP相關(guān)的類(lèi)。解決項(xiàng)目右鍵Build Path→Add Library→Server Runtime→選擇你配置好的Tomcat版本然后Clean一下項(xiàng)目重新編譯。注意這里不要加JRE目錄加的是Server運(yùn)行時(shí)???啟動(dòng)后登錄頁(yè)能開(kāi)一提交就報(bào)數(shù)據(jù)庫(kù)ClassNotFoundException現(xiàn)象Tomcat控制臺(tái)打出一串找不到com.mysql.jdbc.Driver的異常。原因MySQL的JDBC驅(qū)動(dòng)jar沒(méi)有放進(jìn)項(xiàng)目的WEB-INF/lib或者放在但沒(méi)被部署到Tomcat的lib路徑。解決確認(rèn)mysql-connector-java的jar存在于WEB-INF/lib下同時(shí)檢查項(xiàng)目Properties的Deployment Assembly里有沒(méi)有把lib目錄發(fā)布出去。驅(qū)動(dòng)類(lèi)名也要注意MySQL 8之后用com.mysql.cj.jdbc.Driver老版本才寫(xiě)com.mysql.jdbc.Driver。坑4下載文件名中文亂碼或者直接下載失敗現(xiàn)象點(diǎn)免費(fèi)下載后瀏覽器彈出的文件名是亂碼有些直接變成一串加號(hào)。原因HttpServletResponse的Content-Disposition頭默認(rèn)按ISO-8859-1解析中文文件名必須在代碼里做URL編碼。解決第4章代碼里用了URLEncoder.encode先編碼文件名再把加號(hào)替換成%20這種方式對(duì)Chrome、Firefox和Edge都兼容。如果你看到文件流里帶真實(shí)路徑基本可以確定是傳入的fileName參數(shù)傳成了完整磁盤(pán)路徑修正調(diào)用處。坑5付費(fèi)下載后回到頁(yè)面還是提示沒(méi)有下載權(quán)限現(xiàn)象支付都扣款成功了用戶再點(diǎn)下載仍然是「請(qǐng)先付費(fèi)」。原因支付回調(diào)沒(méi)有成功寫(xiě)入download_log或者寫(xiě)入時(shí)expire_time比較的字段類(lèi)型不對(duì)——最常見(jiàn)的是回調(diào)URL配置錯(cuò)了支付平臺(tái)通知到另一個(gè)地址導(dǎo)致租期記錄永遠(yuǎn)沒(méi)有落庫(kù)。解決先查download_log表看有沒(méi)有新增記錄沒(méi)有就看支付平臺(tái)回調(diào)日志核對(duì)notify地址和Controller映射是否一致有記錄但判斷失敗排查是否把過(guò)期時(shí)間存成了字符串導(dǎo)致和NOW()比較時(shí)數(shù)據(jù)庫(kù)做隱式轉(zhuǎn)換。大概率不是代碼邏輯問(wèn)題而是回調(diào)鏈路沒(méi)通。坑6員工登錄后臺(tái)看不到申報(bào)審核菜單現(xiàn)象員工賬號(hào)能登錄但功能菜單里只有個(gè)人資料申報(bào)審核入口消失。原因頁(yè)面用角色標(biāo)識(shí)控制菜單渲染員工記錄的角色字段值和代碼里約定的角色常量不一致常見(jiàn)于初始化數(shù)據(jù)時(shí)少填了角色或者填寫(xiě)了默認(rèn)值。解決先查員工表對(duì)應(yīng)記錄的role字段再去代碼里找到Session里存的角色變量名兩邊的字符串完全對(duì)應(yīng)上。別在頁(yè)面里短接邏輯臨時(shí)把菜單顯示出來(lái)會(huì)繞過(guò)權(quán)限控制。6. 上線前的驗(yàn)證清單與一處權(quán)限加固技巧6.1 驗(yàn)證申報(bào)、審核、公布全鏈路我每次拿到這類(lèi)系統(tǒng)都會(huì)先建一套完整流程走一遍確認(rèn)狀態(tài)流轉(zhuǎn)沒(méi)有斷點(diǎn)。賬號(hào)準(zhǔn)備三個(gè)一個(gè)注冊(cè)用戶、一個(gè)員工、一個(gè)管理員按表格順序操作步驟操作預(yù)期結(jié)果1新用戶注冊(cè)并登錄前臺(tái)跳轉(zhuǎn)首頁(yè)導(dǎo)航欄出現(xiàn)下載和申報(bào)入口2提交一條知識(shí)產(chǎn)權(quán)申報(bào)申報(bào)列表里顯示待審核狀態(tài)3員工登錄后臺(tái)查看待審核列表能看到剛提交的申報(bào)記錄4員工審核通過(guò)前臺(tái)搜索結(jié)果出現(xiàn)該知識(shí)產(chǎn)權(quán)5普通用戶搜索該知識(shí)產(chǎn)權(quán)能搜到且可進(jìn)入詳情頁(yè)如果中間某一步卡住優(yōu)先檢查對(duì)應(yīng)記錄的狀態(tài)字段值不要急著改代碼。絕大多數(shù)申報(bào)鏈路問(wèn)題都出在狀態(tài)機(jī)沒(méi)走通而不是頁(yè)面邏輯壞了。6.2 驗(yàn)證下載權(quán)限與租借時(shí)間邊界下載模塊的驗(yàn)收要點(diǎn)在于「邊界」。免費(fèi)下載要驗(yàn)證未登錄攔截、已登錄直接下載付費(fèi)下載要驗(yàn)證未付費(fèi)跳支付、付費(fèi)后立即下載、過(guò)期后重新跳支付。我把租期設(shè)成1分鐘的測(cè)試數(shù)據(jù)在界面上盯到過(guò)期后看按鈕狀態(tài)是否變化是排查時(shí)間邏輯最有效的方式。6.3 給系統(tǒng)補(bǔ)一層統(tǒng)一登錄攔截對(duì)照一遍Controller清單之后如果確認(rèn)沒(méi)有統(tǒng)一權(quán)限攔截我建議加一個(gè)最基礎(chǔ)的登錄攔截器避免用戶拼URL訪問(wèn)后臺(tái)功能。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception { Object user req.getSession().getAttribute(loginUser); if (user null) { resp.sendRedirect(/login); return false; } return true; } }在Spring配置里注冊(cè)這個(gè)攔截器mvc:interceptors mvc:interceptor mvc:mapping path/user/**/ mvc:exclude-mapping path/user/login/ bean classcom.demo.interceptor.LoginInterceptor/ /mvc:interceptor /mvc:interceptors邏輯說(shuō)明mapping配置了/user/**路徑凡是匹配的前臺(tái)請(qǐng)求都會(huì)先進(jìn)這個(gè)攔截器exclude-mapping把登錄接口排除掉否則登錄頁(yè)面自己也進(jìn)不了。參數(shù)說(shuō)明如果你還要保護(hù)后臺(tái)員工路徑就再配一條/staff/**映射另外加一個(gè)管理員角色判斷。這套配置改動(dòng)小、風(fēng)險(xiǎn)低能在不動(dòng)業(yè)務(wù)代碼的情況下把「未登錄訪問(wèn)」這個(gè)最大的權(quán)限漏洞堵上。以前我接這類(lèi)老JavaWeb項(xiàng)目上來(lái)就開(kāi)始改業(yè)務(wù)代碼結(jié)果被各種隱藏接口和權(quán)限漏洞折騰了一周。從那以后我每接手一套SSM系統(tǒng)第一件事永遠(yuǎn)是先把Controller方法清單捋一遍再補(bǔ)統(tǒng)一登錄攔截然后再動(dòng)其他業(yè)務(wù)代碼。這套知識(shí)產(chǎn)權(quán)管理系統(tǒng)的功能閉環(huán)已經(jīng)很清晰但入口權(quán)限值得你多看一眼。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取