全棧設(shè)計(jì)與部署實(shí)戰(zhàn)拆解)
做個(gè)論文投稿系統(tǒng)選 Java 還是 Python這個(gè)問(wèn)題兩年前我自己做課設(shè)時(shí)糾結(jié)了很久后來(lái)成品是 Java 后端搭配 Python 輕服務(wù)一起用也就是標(biāo)題里拆出來(lái)的 SSM Flask 組合。這個(gè)思路到現(xiàn)在看依然很穩(wěn)而且少見(jiàn)有人把完整方案寫(xiě)明白。這篇博客就從頭到尾拆一遍為什么要這樣組合、數(shù)據(jù)庫(kù)和核心業(yè)務(wù)怎么設(shè)計(jì)、投稿鏈路怎么從 Java 端走到 Flask 端、部署調(diào)試時(shí)哪幾個(gè)坑最容易踩。不管是拿它當(dāng)畢設(shè)起步還是想塞進(jìn)簡(jiǎn)歷當(dāng)完整全棧項(xiàng)目這篇文章都能讓你少走一個(gè)月的彎路。畢業(yè)設(shè)計(jì)里那些“論文投稿系統(tǒng)”“在線(xiàn)投稿系統(tǒng)”“學(xué)術(shù)期刊稿件管理系統(tǒng)”歸根結(jié)底要解決的問(wèn)題是同一件事讓一篇論文從作者手里經(jīng)過(guò)提交、整理、分配、審閱最終給出錄用或退稿結(jié)論。聽(tīng)起來(lái)不復(fù)雜但牽涉的角色有三個(gè)——作者、編輯管理員、審稿專(zhuān)家狀態(tài)流轉(zhuǎn)有四五步再加上文件上傳、查重輔助、郵件通知這些邊角功能工作量立刻就不一樣了。很多同學(xué)喜歡把這類(lèi)系統(tǒng)做“重”不管三七二十一先堆一個(gè)巨型后臺(tái)管理界面里塞十幾個(gè)菜單最后交差時(shí)卻發(fā)現(xiàn)作者端連投稿流程都沒(méi)跑通。我自己動(dòng)手時(shí)的思路是另一套主流程優(yōu)先邊角功能讓位能交給腳本和輕服務(wù)做的事絕不塞進(jìn) Java 主工程里。這篇的標(biāo)題里有 Swagger 調(diào)試文檔、LW 文檔但真正的靈魂不是那些紙面材料而是這套 SSM Flask 雙引擎架構(gòu)本身。下面正式進(jìn)入拆解環(huán)節(jié)。1. 整體設(shè)計(jì)與思路拆解1.1 為什么是 SSM 和 Flask 兩套后端同時(shí)存在先解決所有人看到這個(gè)標(biāo)題時(shí)的第一反應(yīng)論文投稿系統(tǒng)為什么不用單一技術(shù)棧理由有三層。第一SSM 是現(xiàn)階段 Java 后端課設(shè)和面試出現(xiàn)頻率最高的組合之一Spring 管 Bean、SpringMVC 管路由和請(qǐng)求映射、MyBatis 管數(shù)據(jù)庫(kù)操作這套東西“規(guī)整”適合做結(jié)構(gòu)清晰的主業(yè)務(wù)系統(tǒng)。用戶(hù)管理、投稿單 CRUD、審稿分配、權(quán)限攔截都是典型的企業(yè)級(jí) CRUD 場(chǎng)景用 Java 寫(xiě)維護(hù)成本低。第二論文投稿系統(tǒng)逃不開(kāi)一個(gè)“軟性需求”查重輔助。哪怕只是做一個(gè)粗糙的文本相似度提示也能極大提升系統(tǒng)的價(jià)值感。這種東西用 Python 做就是幾行代碼的事分詞、TF-IDF、余弦相似度、向量比較生態(tài)里全是現(xiàn)成的而 Java 做同樣的事情要繞很多彎路。所以 Flask 在這里不是搶 SSM 的飯碗而是補(bǔ) SSM 做不到的部分。第三從工程分層角度看兩套后端只通過(guò) HTTP JSON 通信中間沒(méi)有任何代碼層面的耦合。這意味著任何一邊崩了另一邊還能繼續(xù)跑也意味著你可以在答辯時(shí)說(shuō)清楚“模塊化設(shè)計(jì)”和“服務(wù)解耦”這兩個(gè)詞。這種話(huà)術(shù)在畢業(yè)答辯現(xiàn)場(chǎng)是很加分的。1.2 業(yè)務(wù)模塊劃分三類(lèi)角色與一條主流程在做數(shù)據(jù)庫(kù)表設(shè)計(jì)之前我先把業(yè)務(wù)跑了一遍畫(huà)出三個(gè)角色各自要干的事。作者注冊(cè)登錄、維護(hù)個(gè)人資料、提交論文標(biāo)題、摘要、關(guān)鍵詞、PDF 附件、查看審稿狀態(tài)、接收錄用/退稿通知。審稿專(zhuān)家接收系統(tǒng)分配的稿件、下載論文、填寫(xiě)審稿意見(jiàn)和評(píng)分、給出建議結(jié)論。管理員/編輯管理期刊和欄目、審核投稿單、分配審稿專(zhuān)家、終審論文、發(fā)布錄用結(jié)果。整個(gè)系統(tǒng)最核心的一條流程是作者提交稿件 → 管理員初審 → 系統(tǒng)調(diào)用 Flask 服務(wù)做相似度檢測(cè) → 管理員分配兩位審稿人 → 審稿人打分填寫(xiě)意見(jiàn) → 管理員依據(jù)意見(jiàn)終審 → 結(jié)果回寫(xiě)給作者。流程圖不用工具畫(huà)你在腦子里過(guò)一遍就是一張狀態(tài)機(jī)待初審、初審?fù)ㄟ^(guò)、相似度檢測(cè)中、外審中、終審中、已錄用、已退稿。后面所有開(kāi)發(fā)任務(wù)都是圍繞這張狀態(tài)機(jī)展開(kāi)的。1.3 Flask 在這個(gè)系統(tǒng)里到底承擔(dān)哪幾個(gè)職責(zé)很多人以為 Flask 只是用來(lái)當(dāng)一個(gè)“玩具服務(wù)”展示實(shí)際上我把它用在了三個(gè)很實(shí)際的位置。第一個(gè)是文本相似度檢測(cè)接口。作者提交摘要或正文文本后Java 端把文本 POST 到 Flask 的/api/similarity接口Flask 用 jieba 分詞后計(jì)算 TF-IDF 向量之間的余弦相似度返回一個(gè) 0 到 1 的分?jǐn)?shù)。數(shù)據(jù)庫(kù)里預(yù)置了一批往屆論文摘要作為比對(duì)庫(kù)這個(gè)分?jǐn)?shù)能進(jìn)到投稿單里展示給編輯。第二個(gè)是郵件通知服務(wù)。Java 里用 JavaMail 也能發(fā)郵件但配置繁瑣。Python 的 smtplib 配合 Flask 接口一個(gè) POST 請(qǐng)求傳標(biāo)題、收件人、正文就能把狀態(tài)變化推送出去。輕量干凈和主業(yè)務(wù)解耦。第三個(gè)是數(shù)據(jù)看板的小接口。管理員首頁(yè)要顯示投稿量趨勢(shì)、錄用率、待處理稿件數(shù)如果這些全用 Java 寫(xiě)圖表費(fèi)時(shí)費(fèi)力。Flask 端只需要從數(shù)據(jù)庫(kù)和 Java 連同一個(gè) MySQL查幾個(gè)聚合數(shù)據(jù)返回 JSON 就行。后面你用 ECharts 還是 Highcharts畫(huà)圖的活兒全丟給前端。2. 核心細(xì)節(jié)解析與實(shí)操要點(diǎn)2.1 數(shù)據(jù)庫(kù)表結(jié)構(gòu)九張表的背后邏輯論文投稿系統(tǒng)的數(shù)據(jù)庫(kù)不用搞得很?chē)樔撕诵钠鋵?shí)九張表就夠了。我實(shí)際建表如下表名用途關(guān)鍵字段說(shuō)明user全部三類(lèi)用戶(hù)role 區(qū)分作者/審稿人/管理員paper論文主表title, abstract, keywords, file_path, statuspaper_author論文作者關(guān)聯(lián)表支持一篇論文多個(gè)作者journal期刊表期刊名稱(chēng)、ISSN、周期column_info期刊欄目表一本期刊下多個(gè)欄目review審稿表paper_id, reviewer_id, score, comment, conclusionsubmission_log投稿狀態(tài)日志表追蹤每一步狀態(tài)變化similarity_record查重記錄表保存每次相似度檢測(cè)的分?jǐn)?shù)和比對(duì)源notification站內(nèi)信/郵件通知表type, receiver_id, content, is_read這里提醒一個(gè)新手特別容易犯的錯(cuò)不要為“審稿專(zhuān)家填意見(jiàn)”單獨(dú)建一張表然后和 paper 做復(fù)雜關(guān)聯(lián)。直接建review表一篇文章對(duì)應(yīng)兩條記錄兩位審稿人分?jǐn)?shù)和意見(jiàn)全部落在這一張表里。查詢(xún)時(shí)WHERE paper_id ? AND reviewer_id ?就能拿單條審稿結(jié)果漲到多輪外審也好擴(kuò)展。2.2 SSM 端接口設(shè)計(jì)心得順序很重要先定接口再寫(xiě)代碼不是一個(gè)庫(kù)建好就開(kāi)寫(xiě)。作者端核心接口是/api/paper/submit管理員端是/api/paper/assign審稿人端是/api/review/update。這三個(gè)接口搭起來(lái)系統(tǒng)基本面就穩(wěn)了。我用 SpringMVC 的 Controller 層做請(qǐng)求入口Service 層管業(yè)務(wù)邏輯Mapper 層用 MyBatis 做數(shù)據(jù)訪(fǎng)問(wèn)這套分層都不用再解釋是 SSM 項(xiàng)目的標(biāo)配姿勢(shì)。值得展開(kāi)講的是文件上傳這個(gè)接口。論文附件的路徑處理是投稿系統(tǒng)里最容易出 bug 的地方。我推薦的方案是配置文件里寫(xiě)一個(gè)upload.pathController 層讀配置把文件保存在upload.path/論文ID_時(shí)間戳.pdf在數(shù)據(jù)庫(kù)里只存相對(duì)路徑。這樣一來(lái)整個(gè)項(xiàng)目換機(jī)器部署時(shí)只需要改配置文件里的一個(gè)絕對(duì)路徑開(kāi)頭比把路徑硬編碼到處丟強(qiáng)一萬(wàn)倍。2.3 Flask 服務(wù)端怎么和 Java 端做優(yōu)雅對(duì)接這是這個(gè)項(xiàng)目最出彩的一部分我需要把細(xì)節(jié)說(shuō)得更清楚。Java 端通過(guò)RestTemplate或者HttpClientUtils發(fā)起 HTTP 請(qǐng)求向 Flask 的接口傳 JSON。比如相似度檢測(cè)Java 那里大概是這樣// Java SSM 端調(diào)用 Flask 服務(wù) String flaskUrl http://localhost:5001/api/similarity; JSONObject param new JSONObject(); param.put(text, paper.getAbstract()); param.put(targetId, paper.getId()); // 使用 HttpURLConnection 或 RestTemplate 發(fā)起 POST 請(qǐng)求 // 返回結(jié)果 {similarity: 0.832, status: ok}Flask 端對(duì)應(yīng)的接收代碼就是from flask import Flask, request, jsonify import jieba from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity app Flask(__name__) app.route(/api/similarity, methods[POST]) def similarity(): data request.get_json() text data.get(text, ) # 這里從數(shù)據(jù)庫(kù)讀比對(duì)庫(kù)文本做分詞、向量化、相似度計(jì)算 # 返回 jsonify({similarity: 0.832, status: ok})有幾個(gè)關(guān)鍵點(diǎn)必須說(shuō)明。第一是 Flask 服務(wù)要單獨(dú)跑在一個(gè)端口上不要和 Tomcat 搶 8080我用的是 5001。第二是 Java 端調(diào)用 Flask 時(shí)要配置好超時(shí)時(shí)間否則 Flask 那邊卡住Java 線(xiàn)程也會(huì)一起掛。第三是兩邊統(tǒng)一用 JSON 通信不要搞 XML不要搞自定義二進(jìn)制協(xié)議JSON 是兩種語(yǔ)言之間最沒(méi)有爭(zhēng)議的通用語(yǔ)言。2.4 輕量數(shù)據(jù)庫(kù)的選擇MySQL 還是 SQLite標(biāo)題里提了“輕量化數(shù)據(jù)庫(kù)”很多同學(xué)會(huì)問(wèn)那就用 SQLite 好了不能一概而論。如果你的系統(tǒng)要跑在 Tomcat 里同時(shí)段可能有多個(gè)用戶(hù)并發(fā)訪(fǎng)問(wèn)SQLite 在寫(xiě)入鎖方面會(huì)讓你很痛苦。MySQL 才是穩(wěn)妥選擇。但我在 Flask 端做比對(duì)庫(kù)查詢(xún)時(shí)反而使用了 SQLite 作為只讀庫(kù)因?yàn)楸葘?duì)不需要頻繁寫(xiě)入只讀場(chǎng)景下 SQLite 的開(kāi)銷(xiāo)更小也能避免 Java 和 Python 兩端同時(shí)對(duì)同一個(gè) MySQL 表做操作時(shí)的鎖競(jìng)爭(zhēng)。最合理的方案是Java 端和 Flask 端共享同一個(gè) MySQL 數(shù)據(jù)庫(kù)Java 負(fù)責(zé)寫(xiě)Flask 大部分時(shí)間只讀偶爾比如記錄檢測(cè)分?jǐn)?shù)才寫(xiě)similarity_record表。這種做法在答辯時(shí)也可以說(shuō)成“讀寫(xiě)分離思想的簡(jiǎn)化版本”雖然沒(méi)有中間件但邏輯上已經(jīng)是那套思路了。3. 實(shí)操過(guò)程與核心環(huán)節(jié)實(shí)現(xiàn)3.1 環(huán)境準(zhǔn)備與項(xiàng)目初始化流程下面這些操作是我在實(shí)際搭建時(shí)一步步走下來(lái)的照著做不會(huì)有問(wèn)題。按我平時(shí)最順手的順序來(lái)先把 Java 環(huán)境和構(gòu)建工具裝好。JDK 我用的是 1.8不是因?yàn)榕f而是 SSM 這個(gè)組合對(duì) 8 的兼容性最好Spring 5.x MyBatis 3.x 在這個(gè)版本下不會(huì)出幺蛾子。Maven 裝好之后用mvn archetype:generate創(chuàng)建一個(gè) war 包結(jié)構(gòu)的 web 項(xiàng)目或者直接在 IDEA 里新建 Spring Initializr 項(xiàng)目再手動(dòng)導(dǎo)依賴(lài)。IDEA 能省很多事但如果你是非 IDEA 黨那用 Maven 命令也沒(méi)問(wèn)題。核心依賴(lài)要加這幾個(gè)spring-webmvc、spring-jdbc、mybatis、mybatis-spring、mysql-connector-java、jackson-databindJSON 處理、commons-fileupload文件上傳。第一次做 SSM 整合的同學(xué)最容易卡在 Spring 和 SpringMVC 的配置文件上我給出一個(gè)最簡(jiǎn)但是能跑通的骨架思路spring-context.xml里配數(shù)據(jù)源和 MyBatis 的SqlSessionFactory開(kāi)啟 Mapper 掃描。spring-mvc.xml里開(kāi)啟注解驅(qū)動(dòng)配置視圖解析器和文件上傳解析器。web.xml里配置 Spring 的ContextLoaderListener和 SpringMVC 的DispatcherServlet。Flask 端的準(zhǔn)備更輕。Python 3.8 以上版本裝好flask、jieba、scikit-learn、numpy就行。不用建虛擬環(huán)境也問(wèn)題不大但強(qiáng)烈建議建一個(gè)否則 sklearn 的依賴(lài)會(huì)把你的全局 Python 環(huán)境搞得很亂。用python -m venv venv一鍵創(chuàng)建之后所有依賴(lài)都裝進(jìn)這個(gè)虛擬環(huán)境里。3.2 手把手實(shí)現(xiàn)一個(gè)投稿主流程從作者登錄到稿件成功入庫(kù)這一小節(jié)完全照著敲跑通它你就掌握了 70% 的 SSM 核心操作。第一步作者提交投稿表單包含標(biāo)題、摘要、關(guān)鍵詞、PDF 文件。前端發(fā)一個(gè)multipart/form-data請(qǐng)求到 Java 后臺(tái)。Controller RequestMapping(/api/paper) public class PaperController { PostMapping(/submit) ResponseBody public Result submit(RequestParam(title) String title, RequestParam(abstractText) String abstractText, RequestParam(keywords) String keywords, RequestParam(file) MultipartFile file) { // 1. 保存文件到 upload.path // 2. 插入 paper 表status 待初審 // 3. 插入 submission_log 記錄 // 4. 異步調(diào)用 Flask 相似度接口 return Result.success(); } }第二步管理員初審?fù)ㄟ^(guò)后點(diǎn)擊“分配審稿人”。Java 端在review表里插入兩條記錄分別填兩個(gè)審稿人同時(shí)把paper.status改成“外審中”。這一步最關(guān)鍵的是要保證事務(wù)性即兩個(gè)insert和一個(gè)update必須要么都成功、要么都失敗。Spring 的Transactional注解直接標(biāo)在 Service 方法上就能搞定手動(dòng)去 commit 和 rollback 就太傻了。第三步審稿人提交審稿意見(jiàn)。這個(gè)接口要做一層權(quán)限校驗(yàn)當(dāng)前登錄用戶(hù)的角色是審稿人并且review表里能查到他確實(shí)被分配了這篇論文否則直接返回 403。用 SSM 的攔截器HandlerInterceptor做通用攔截再配合一層細(xì)粒度判斷邏輯就完整了。3.3 Flask 相似度查重模塊的實(shí)現(xiàn)細(xì)節(jié)這個(gè)模塊雖然代碼量不大但細(xì)節(jié)能讓整個(gè)項(xiàng)目的技術(shù)含量上一個(gè)臺(tái)階。我用jieba.cut對(duì)文本做中文分詞去掉停用詞然后用jieba.analyse.extract_tags提取關(guān)鍵詞再用 TF-IDF 算法把文本轉(zhuǎn)成向量最后計(jì)算余弦相似度。中文文本處理最忌諱的是直接按空格切分——英文文本分詞用空格就可以但中文不提前分詞的話(huà)比較結(jié)果就是一堆亂碼級(jí)別的垃圾。第一次跑相似度檢測(cè)時(shí)如果發(fā)現(xiàn)分?jǐn)?shù)高得離譜先檢查分詞環(huán)節(jié)有沒(méi)有生效而不是急著調(diào)閾值。比對(duì)庫(kù)的數(shù)據(jù)來(lái)源很關(guān)鍵。我自己準(zhǔn)備了兩種數(shù)據(jù)一是本地爬取整合的論文摘要存成 CSV二是系統(tǒng)歷史投稿的已錄論文摘要。每次檢測(cè)把當(dāng)前文本逐個(gè)和比對(duì)庫(kù)里的文本做相似度計(jì)算取最高分返回。比對(duì)庫(kù)只有幾百條時(shí)單次檢測(cè)耗時(shí)也就幾百毫秒完全夠用。如果數(shù)據(jù)量到達(dá)上萬(wàn)條可以先用關(guān)鍵詞粗篩出候選集再對(duì)候選集做精確計(jì)算這在小項(xiàng)目里屬于可選的性能優(yōu)化。Flask 端返回的數(shù)據(jù)格式我建議統(tǒng)一這樣{ status: ok, similarity: 0.67, match_source: 《基于大數(shù)據(jù)的教學(xué)設(shè)計(jì)研究》, code: 200 }Java 端拿到這個(gè) JSON 后解析把similarity存進(jìn)similarity_record表把match_source展示到管理員界面上。格式統(tǒng)一這一點(diǎn)必須在開(kāi)發(fā)第一天就定好不然后面 Java 端和 Flask 端各寫(xiě)各的溝通成本直接翻倍。3.4 部署方式本地部署、Windows 與 Linux本地跑通這套系統(tǒng)用兩臺(tái)“服務(wù)器進(jìn)程”就能完成。Tomcat 跑 SSM 的 war 包端口 8080python app.py跑 Flask端口 5001。中間不要用反向代理干預(yù)調(diào)試時(shí)直接用端口訪(fǎng)問(wèn)最直觀。部署到 Windows 服務(wù)器時(shí)可以保留這套方案但有一個(gè)必踩的坑Flask 的附件存儲(chǔ)路徑和日志路徑最好不要和 Java 端共享同一個(gè)目錄。兩邊同時(shí)讀寫(xiě)同一個(gè)目錄權(quán)限沖突會(huì)有無(wú)休止的報(bào)錯(cuò)。我的做法是在 D 盤(pán)下分兩個(gè)目錄D:/paper_system/java_upload和D:/paper_system/flask_storage。部署到 Linux 服務(wù)器時(shí)推薦用 gunicorn 來(lái)跑 Flask不要再用python app.py裸跑。命令大概這樣gunicorn -w 2 -b 0.0.0.0:5001 app:app這條命令的意思是用 2 個(gè) worker 進(jìn)程跑 Flask 服務(wù)監(jiān)聽(tīng)所有網(wǎng)卡的 5001 端口。Java 端部署在 Tomcat 里前后端分離的話(huà)再把 HTML 靜態(tài)頁(yè)面丟到 Tomcat 的webapps/ROOT里就行。Linux 環(huán)境下的文件路徑不用 D:/ 這種寫(xiě)法全部換成絕對(duì)路徑比如/data/paper_system/java_upload。4. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄寫(xiě)這節(jié)是因?yàn)槲耶?dāng)初確實(shí)花了很多個(gè)晚上在這些問(wèn)題上有些問(wèn)題甚至讓我懷疑人生。這里直接給速查表能幫你少走彎路。4.1 Flask 附件路徑錯(cuò)誤這個(gè)問(wèn)題在標(biāo)題熱詞里被提到大概率也是所有做同類(lèi)系統(tǒng)的人共同痛點(diǎn)。癥狀是 Java 端上傳文件成功但 Flask 端讀取附件時(shí)找不到文件或者 Windows 路徑的正反斜杠在 Linux 上完全不認(rèn)。我最終的解法是所有路徑不僅在配置文件里集中管理而且始終用Path或os.path.join來(lái)拼接。Flask 端接收 Java 傳來(lái)的路徑時(shí)要對(duì)方只給相對(duì)路徑拼盤(pán)符和上級(jí)目錄的工作交給配置文件。不要寫(xiě)死/var/www/...也不要在 Windows 上寫(xiě)C:\\...環(huán)境一換就崩。4.2 SSM 和 Flask 的端口沖突與跨域兩個(gè)服務(wù)都跑在同一臺(tái)機(jī)器上端口沖突本來(lái)就少見(jiàn)但如果前端頁(yè)面是純靜態(tài)頁(yè)面跨域問(wèn)題就很常見(jiàn)了。Java 端接口端口 8080Flask 接口端口 5001頁(yè)面從 8080 調(diào) 5001 的接口瀏覽器默認(rèn)會(huì)攔跨域請(qǐng)求。解決方案有兩種。一種是 Java 端在 Controller 層寫(xiě)一個(gè)轉(zhuǎn)發(fā)接口前端只調(diào) 8080由 Java 作為中間人轉(zhuǎn)發(fā)到 5001。另一種是給 Flask 加flask-cors擴(kuò)展允許跨域。我用了后者但答辯時(shí)兩種方案都要能說(shuō)清楚。4.3 相似度檢測(cè)不準(zhǔn)的排查檢測(cè)出來(lái)的相似度全為 0 或異常高別急著懷疑算法先檢查數(shù)據(jù)預(yù)處理。兩個(gè)關(guān)鍵詞停用詞表和比對(duì)庫(kù)質(zhì)量。中英文標(biāo)點(diǎn)、常見(jiàn)虛詞“的”“了”“一個(gè)”“我們”都要在預(yù)處理時(shí)去掉否則這些詞會(huì)干擾 TF-IDF 權(quán)重。比對(duì)庫(kù)幾千條全是同一主題時(shí)任何新投稿都會(huì)得到高相似度這同樣是數(shù)據(jù)問(wèn)題不是算法問(wèn)題。4.4 投稿狀態(tài)不一致的深層原因狀態(tài)機(jī)設(shè)計(jì)得好不好直接決定后面用起來(lái)順不順。這里想分享一個(gè)體會(huì)submission_log這張日志表不是可有可無(wú)的。我一開(kāi)始為了偷懶沒(méi)建這張表結(jié)果有一次復(fù)審時(shí)狀態(tài)從“外審中”直接跳成了“已錄用”中間丟了“終審中”這一步給管理員帶來(lái)了困擾。加完日志表以后每次狀態(tài)變更都記錄操作者、時(shí)間、舊狀態(tài)、新?tīng)顟B(tài)出問(wèn)題能倒查是哪一步跳變排查效率翻倍。4.5 Flask 服務(wù)的安全加固小常識(shí)這個(gè)系統(tǒng)里 Flask 因?yàn)榕茉?5001 端口容易被忽略但任何公網(wǎng)服務(wù)都應(yīng)該做基本的安全加固。我不展開(kāi)講什么攻擊手法只說(shuō)幾個(gè)防御的習(xí)慣。第一Flask 的 debug 模式在生產(chǎn)環(huán)境必須關(guān)閉不然錯(cuò)誤頁(yè)面會(huì)泄露源碼路徑和調(diào)用棧。第二前端提交的內(nèi)容不能直接扔到 Flask 模板的渲染變量里要用轉(zhuǎn)義或讓前端框架做輸出編碼避免留下注入空間。第三Java 端調(diào)用 Flask 的 URL 不要寫(xiě)死帶密鑰或口令的參數(shù)接口鑒權(quán)用固定的一次性 token 就行。這些習(xí)慣在任何 Web 項(xiàng)目里都適用屬于寫(xiě)代碼的基本素養(yǎng)。4.6 問(wèn)題速查表現(xiàn)象可能原因解決辦法上傳文件后 Flask 找不到文件路徑拼接不一致使用相對(duì)路徑/絕對(duì)路徑混亂統(tǒng)一由配置文件維護(hù)路徑用 os.path.join 拼接Java 調(diào) Flask 接口超時(shí)Flask 未啟動(dòng)端口錯(cuò)誤防火墻放行 5001 失敗先 curl 直接測(cè)試 Flask 接口再排查 Java 端 timeout 配置相似度全部為 0比對(duì)庫(kù)為空分詞沒(méi)生效停用詞沒(méi)有過(guò)濾檢查比對(duì)庫(kù)數(shù)據(jù)加入 jieba 分詞配置停用詞表投稿狀態(tài)跳變?nèi)鄙贍顟B(tài)日志表代碼里硬編碼狀態(tài)值增加 submission_log 表所有狀態(tài)以常量類(lèi)管理頁(yè)面跨域報(bào)錯(cuò)端口不一致觸發(fā)瀏覽器同源策略Flask 端開(kāi)啟 CORS或用 Java 做轉(zhuǎn)發(fā)接口郵件通知發(fā)不出去SMTP 配置錯(cuò)誤QQ/163 郵箱需要授權(quán)碼而非密碼檢查授權(quán)碼檢查郵箱是否開(kāi)啟 SMTP 服務(wù)不要小看這張表它是我把整個(gè)開(kāi)發(fā)過(guò)程中真實(shí)踩過(guò)的坑濃縮出來(lái)的。里面每一條都有當(dāng)時(shí)的慌亂和后續(xù)的豁然開(kāi)朗。5. 個(gè)人的幾點(diǎn)體會(huì)這個(gè)項(xiàng)目真正讓我學(xué)到東西的不是 SSM 或者 Flask 的某一個(gè)語(yǔ)法或框架特性而是如何讓兩套不同語(yǔ)言的技術(shù)棧在一起協(xié)同工作。最初我也怕麻煩覺(jué)得“一個(gè) SSM 全搞定”就夠了。但當(dāng)我把相似度檢測(cè)用 Java 寫(xiě)了一版預(yù)處理工作量和代碼復(fù)雜度肉眼可見(jiàn)地暴漲之后我才理解選型真的是做系統(tǒng)的第一決策。Java 的強(qiáng)項(xiàng)是業(yè)務(wù)建模和穩(wěn)定Python 的強(qiáng)項(xiàng)是快速算法實(shí)現(xiàn)和數(shù)據(jù)處理雙框架不是炫技是讓合適的技術(shù)做合適的事。最后再分享一個(gè)實(shí)用的小技巧。如果你覺(jué)得論文投稿系統(tǒng)的管理后臺(tái)太樸素不要急著在 Java 端堆前端代碼直接在 Flask 端寫(xiě)一個(gè)簡(jiǎn)單的頁(yè)面把投稿趨勢(shì)、錄用率、等待外審數(shù)量這些聚合數(shù)據(jù)渲染出來(lái)。每次啟動(dòng)系統(tǒng)后訪(fǎng)問(wèn)一次 5001 端口就能看到一個(gè)小型數(shù)據(jù)看板看起來(lái)像高級(jí)很多的樣子。實(shí)際上背后的代碼不到一百行但實(shí)際效果很可觀答辯現(xiàn)場(chǎng)它也是吸引老師注意的亮點(diǎn)之一。這套“SSM 做穩(wěn)定、Flask 做算法和輔助”的混合架構(gòu)思路也不局限于論文投稿系統(tǒng)——失物招領(lǐng)平臺(tái)、農(nóng)產(chǎn)品價(jià)格可視化平臺(tái)、校園互助系統(tǒng)這些課設(shè)、畢設(shè)題目里大量存在“需要業(yè)務(wù)系統(tǒng) 需要智能推薦/算法輔助”的場(chǎng)景思路都是通用的。下一次做項(xiàng)目你也會(huì)發(fā)現(xiàn)而代碼和接口彼此分離會(huì)讓你在擴(kuò)展功能時(shí)體會(huì)到前所未有的輕松。