布系統(tǒng)實(shí)戰(zhàn):從數(shù)據(jù)庫(kù)設(shè)計(jì)到部署答辯全鏈路)
簡(jiǎn)介這是一套基于JavaWeb的新聞發(fā)布系統(tǒng)完整項(xiàng)目資料面向計(jì)算機(jī)專業(yè)學(xué)生、Java初學(xué)者及需要項(xiàng)目實(shí)戰(zhàn)經(jīng)驗(yàn)的開發(fā)者可用于畢業(yè)設(shè)計(jì)參考、課程設(shè)計(jì)模板或求職作品積累。資源包含完整源代碼、數(shù)據(jù)庫(kù)SQL腳本與配套畢業(yè)論文覆蓋新聞管理、評(píng)論、用戶、鏈接、圖片上傳等核心模塊Servlet與DAO分層結(jié)構(gòu)清晰便于理解JavaWeb開發(fā)流程。壓縮包共556個(gè)文件約11.65MB以js腳本、png與gif圖片、css樣式、html與jsp頁(yè)面為主另有java源碼、class文件、jar依賴、xml配置及sql建庫(kù)腳本前端資源與后端邏輯齊備。目前已有123人學(xué)習(xí)下載。通過研讀源碼與論文讀者可掌握新聞發(fā)布系統(tǒng)的整體架構(gòu)、數(shù)據(jù)庫(kù)表設(shè)計(jì)、Servlet請(qǐng)求處理與頁(yè)面交互實(shí)現(xiàn)并借助部署說明快速運(yùn)行項(xiàng)目適合作為二次開發(fā)或功能擴(kuò)展的起點(diǎn)。1. JavaWeb新聞發(fā)布系統(tǒng)從建庫(kù)到部署一套能寫進(jìn)畢業(yè)論文的完整鏈路很多計(jì)算機(jī)專業(yè)的學(xué)生在做畢業(yè)設(shè)計(jì)時(shí)都會(huì)遇到同一個(gè)尷尬選題定了“新聞發(fā)布系統(tǒng)”代碼從網(wǎng)上拉了一份數(shù)據(jù)庫(kù)也導(dǎo)入了但真到答辯時(shí)被問一句“你這個(gè)權(quán)限表怎么設(shè)計(jì)的”“SQL注入怎么防的”就答不上來。問題不在于系統(tǒng)有多復(fù)雜而在于整個(gè)鏈路是拼湊的不是自己跑通的。JavaWeb新聞發(fā)布系統(tǒng)這個(gè)題目本身并不新鮮但它恰好覆蓋了JavaWeb開發(fā)中最核心的幾個(gè)環(huán)節(jié)JSP/Servlet請(qǐng)求流轉(zhuǎn)、JDBC數(shù)據(jù)庫(kù)操作、會(huì)話管理、分頁(yè)查詢、前后臺(tái)權(quán)限隔離。把這套東西從頭搭一遍比看十篇理論文章都管用。這篇文章面向兩類人一是需要完成畢業(yè)論文和項(xiàng)目交付的本科生二是剛轉(zhuǎn)JavaWeb方向、想找一個(gè)完整案例練手的開發(fā)者。接下來我會(huì)按“建庫(kù)→搭工程→寫核心功能→避坑→進(jìn)階”的順序把每個(gè)環(huán)節(jié)的關(guān)鍵參數(shù)和常見翻車點(diǎn)講清楚。2. 數(shù)據(jù)庫(kù)SQL設(shè)計(jì)新聞發(fā)布系統(tǒng)的表結(jié)構(gòu)怎么定才不返工2.1 五張核心表?yè)纹鹫麄€(gè)系統(tǒng)新聞發(fā)布系統(tǒng)的數(shù)據(jù)庫(kù)設(shè)計(jì)不需要太復(fù)雜但也不能只有一張新聞表。常見做法是拆成五張表用戶表、新聞分類表、新聞表、評(píng)論表、角色權(quán)限表。這樣拆的好處是后續(xù)加功能時(shí)不用大改結(jié)構(gòu)。先看用戶表。字段包括user_id主鍵自增、username登錄名唯一索引、password存MD5或BCrypt哈希不要明文、real_name、role_id外鍵關(guān)聯(lián)角色表、create_time。這里有個(gè)容易忽略的點(diǎn)username要加唯一索引否則注冊(cè)時(shí)并發(fā)插入會(huì)出現(xiàn)重復(fù)用戶名。CREATE TABLE t_user ( user_id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL COMMENT 登錄名, password VARCHAR(128) NOT NULL COMMENT BCrypt哈希值, real_name VARCHAR(50) DEFAULT NULL, role_id INT NOT NULL DEFAULT 2 COMMENT 1管理員 2普通編輯, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (user_id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;新聞表是核心。字段設(shè)計(jì)上title用VARCHAR(200)content用TEXT或MEDIUMTEXTcategory_id關(guān)聯(lián)分類表author_id關(guān)聯(lián)用戶表status用TINYINT表示草稿/已發(fā)布/下架publish_time記錄發(fā)布時(shí)間view_count記錄閱讀量。這里注意content字段如果預(yù)計(jì)超過64KB必須用MEDIUMTEXT否則會(huì)被截?cái)唷REATE TABLE t_news ( news_id INT NOT NULL AUTO_INCREMENT, title VARCHAR(200) NOT NULL, content MEDIUMTEXT NOT NULL, category_id INT NOT NULL, author_id INT NOT NULL, status TINYINT DEFAULT 0 COMMENT 0草稿 1已發(fā)布 2下架, view_count INT DEFAULT 0, publish_time DATETIME DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (news_id), KEY idx_category (category_id), KEY idx_status_publish (status, publish_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;評(píng)論表需要支持嵌套評(píng)論的話加一個(gè)parent_id字段指向父評(píng)論。分類表就是category_id和category_name兩個(gè)核心字段。角色權(quán)限表可以簡(jiǎn)化為role_id和role_name如果要做細(xì)粒度權(quán)限控制再加一張role_permission關(guān)聯(lián)表。2.2 索引和字符集的兩個(gè)硬性決定字符集必須用utf8mb4不要用utf8。MySQL的utf8其實(shí)是utf8mb3存不了emoji和部分生僻字新聞內(nèi)容里出現(xiàn)特殊符號(hào)就會(huì)報(bào)錯(cuò)。這個(gè)坑我在三個(gè)項(xiàng)目里都遇到過血淚經(jīng)驗(yàn)就是建庫(kù)時(shí)直接指定utf8mb4。索引方面新聞列表頁(yè)最常用的查詢是“按狀態(tài)和發(fā)布時(shí)間倒序分頁(yè)”所以建聯(lián)合索引idx_status_publish(status, publish_time)。注意索引順序不能反status區(qū)分度高且查詢條件里等值匹配放前面publish_time用于排序放后面。如果反過來建排序時(shí)用不到索引數(shù)據(jù)量上萬后分頁(yè)會(huì)明顯變慢。提示建完表后執(zhí)行 EXPLAIN 看一下新聞列表查詢的執(zhí)行計(jì)劃確認(rèn) type 不是 ALLExtra 里沒有 Using filesort。3. 工程搭建與核心功能實(shí)現(xiàn)從IDEA配置到分頁(yè)查詢落地3.1 IDEA運(yùn)行JavaWeb項(xiàng)目的關(guān)鍵配置現(xiàn)在用IDEA跑JavaWeb項(xiàng)目常見做法是Maven管理依賴加Tomcat插件或外置Tomcat。先說依賴pom.xml里至少要引這幾個(gè)javax.servlet-apiscope設(shè)為provided、mysql-connector-java、jstl、druid連接池。版本選擇上MySQL 8.x對(duì)應(yīng)connector 8.0.xMySQL 5.7對(duì)應(yīng)connector 5.1.x版本不匹配會(huì)報(bào)“Unknown system variable”或時(shí)區(qū)錯(cuò)誤。dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.20/version /dependency dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependencyIDEA里配置Tomcat的步驟Run → Edit Configurations → 加Tomcat Server → Local → 在Deployment標(biāo)簽頁(yè)加Artifact選war exploded→ Application context設(shè)成“/news”。這里有個(gè)高頻翻車點(diǎn)如果Artifact里沒有war exploded選項(xiàng)說明Project Structure → Artifacts里沒配置需要手動(dòng)加Web Application: Exploded → From Modules。數(shù)據(jù)庫(kù)連接配置建議單獨(dú)放一個(gè)druid.properties文件不要硬編碼在Java里。配置項(xiàng)包括url、username、password、initialSize、maxActive、validationQuery。url里必須加useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai否則中文亂碼和時(shí)間差8小時(shí)兩個(gè)問題會(huì)同時(shí)出現(xiàn)。3.2 新聞列表分頁(yè)查詢的SQL與Java實(shí)現(xiàn)分頁(yè)是新聞系統(tǒng)里最核心的查詢邏輯。MySQL分頁(yè)用LIMIT offset, size但offset大了之后性能會(huì)下降。常見優(yōu)化做法是先查主鍵再回表不過對(duì)于畢業(yè)設(shè)計(jì)級(jí)別的數(shù)據(jù)量幾千到幾萬條直接LIMIT就夠了。SELECT n.news_id, n.title, n.view_count, n.publish_time, c.category_name, u.real_name FROM t_news n LEFT JOIN t_category c ON n.category_id c.category_id LEFT JOIN t_user u ON n.author_id u.user_id WHERE n.status 1 ORDER BY n.publish_time DESC LIMIT ?, ?;Java層用PreparedStatement設(shè)置參數(shù)offset (pageNum - 1) * pageSize。pageSize一般設(shè)10或15pageNum從請(qǐng)求參數(shù)里取要做邊界校驗(yàn)如果pageNum小于1就設(shè)為1如果超過總頁(yè)數(shù)就設(shè)為總頁(yè)數(shù)??傢?yè)數(shù)通過一條COUNT查詢得到。public PageResultNewsVO queryByPage(int pageNum, int pageSize) { if (pageNum 1) pageNum 1; int total newsDao.countPublished(); int totalPages (int) Math.ceil((double) total / pageSize); if (pageNum totalPages totalPages 0) pageNum totalPages; int offset (pageNum - 1) * pageSize; ListNewsVO list newsDao.selectByPage(offset, pageSize); PageResultNewsVO result new PageResult(); result.setList(list); result.setPageNum(pageNum); result.setTotalPages(totalPages); result.setTotal(total); return result; }參數(shù)說明pageNum是當(dāng)前頁(yè)碼pageSize是每頁(yè)條數(shù)offset是SQL跳過的行數(shù)。注意LIMIT后面的兩個(gè)參數(shù)都是int不要傳字符串否則MySQL會(huì)報(bào)語(yǔ)法錯(cuò)誤。另外countPublished和selectByPage的WHERE條件必須一致否則會(huì)出現(xiàn)“總數(shù)對(duì)不上列表?xiàng)l數(shù)”的玄學(xué)問題。3.3 新聞發(fā)布與富文本內(nèi)容的入庫(kù)處理發(fā)布新聞的Servlet接收表單參數(shù)后先做三件事校驗(yàn)title非空且長(zhǎng)度不超過200、校驗(yàn)category_id存在、對(duì)content做XSS過濾。XSS過濾可以用Jsoup的clean方法保留基本標(biāo)簽去掉script和on事件屬性。String safeContent Jsoup.clean(rawContent, Safelist.basicWithImages());入庫(kù)時(shí)用PreparedStatement不要用Statement拼接SQL。拼接SQL是SQL注入的直接原因答辯時(shí)被問到這一點(diǎn)答不上來會(huì)很扣分。publish_time在status設(shè)為1時(shí)取當(dāng)前時(shí)間草稿狀態(tài)設(shè)為null。注意如果content里包含HTML標(biāo)簽數(shù)據(jù)庫(kù)字段用MEDIUMTEXTJSP頁(yè)面輸出時(shí)用c:out value${news.content} escapeXmlfalse/escapeXml設(shè)為false才能渲染HTML但前提是已經(jīng)做過XSS過濾。4. 避坑與排查新聞發(fā)布系統(tǒng)開發(fā)中最容易翻車的5個(gè)點(diǎn)4.1 中文亂碼現(xiàn)象是頁(yè)面顯示問號(hào)原因是字符集鏈路沒統(tǒng)一現(xiàn)象新聞標(biāo)題在數(shù)據(jù)庫(kù)里看是正常的但頁(yè)面上顯示成“???”或亂碼。原因通常有三層數(shù)據(jù)庫(kù)連接url沒加characterEncodingutf8、JSP頁(yè)面pageEncoding沒設(shè)UTF-8、Tomcat的server.xml里Connector沒加URIEncodingUTF-8。解決方法是三層都檢查一遍缺哪層補(bǔ)哪層。最穩(wěn)妥的做法是建庫(kù)時(shí)就指定utf8mb4連接url加useUnicodetruecharacterEncodingutf8JSP頂部加% page contentTypetext/html;charsetUTF-8 languagejava %。4.2 數(shù)據(jù)庫(kù)連接報(bào)時(shí)區(qū)錯(cuò)誤現(xiàn)象是啟動(dòng)時(shí)報(bào)The server time zone value原因是MySQL 8.x默認(rèn)時(shí)區(qū)與JVM不一致現(xiàn)象項(xiàng)目啟動(dòng)或第一次查數(shù)據(jù)庫(kù)時(shí)拋異?!癟he server time zone value ?D1ú±ê×?ê±?? is unrecognized”。原因是MySQL 8.x的默認(rèn)時(shí)區(qū)是系統(tǒng)時(shí)區(qū)而JDBC驅(qū)動(dòng)需要明確的時(shí)區(qū)標(biāo)識(shí)。解決方法是在url后面加serverTimezoneAsia/Shanghai。如果用的是MySQL 5.7加不加都行但建議統(tǒng)一加上避免換環(huán)境后出問題。4.3 分頁(yè)查詢總數(shù)與列表不一致現(xiàn)象是最后一頁(yè)多出空數(shù)據(jù)或漏數(shù)據(jù)原因是COUNT和SELECT的WHERE條件不一致現(xiàn)象新聞列表顯示共23條每頁(yè)10條但翻到第3頁(yè)只有2條或者第3頁(yè)顯示的是重復(fù)數(shù)據(jù)。原因通常是count查詢用了status1而select查詢忘了加status1或者JOIN的表不同導(dǎo)致行數(shù)變化。解決方法是把WHERE條件抽成一個(gè)常量或方法count和select共用。另外如果用了LEFT JOIN且關(guān)聯(lián)表有一對(duì)多關(guān)系count要用COUNT(DISTINCT n.news_id)。4.4 富文本內(nèi)容被截?cái)喱F(xiàn)象是長(zhǎng)新聞保存后只剩前半段原因是字段類型用了TEXT而不是MEDIUMTEXT現(xiàn)象一篇帶圖片和長(zhǎng)段落的新聞發(fā)布后再編輯時(shí)發(fā)現(xiàn)后半段內(nèi)容丟了。原因是MySQL的TEXT類型最大64KButf8mb4下大約只能存16000個(gè)漢字超過就被截?cái)?。解決方法是把content字段改成MEDIUMTEXT最大16MB如果還不夠就用LONGTEXT。改字段用ALTER TABLE t_news MODIFY content MEDIUMTEXT NOT NULL;。4.5 會(huì)話失效后操作報(bào)空指針現(xiàn)象是登錄后放置一段時(shí)間再點(diǎn)發(fā)布頁(yè)面報(bào)500原因是session過期后未做攔截現(xiàn)象用戶登錄后去泡了杯咖啡回來點(diǎn)“發(fā)布新聞”頁(yè)面直接500錯(cuò)誤后臺(tái)日志顯示NullPointerException。原因是session超時(shí)后request.getSession().getAttribute(user)返回null后續(xù)代碼直接調(diào)用了user.getUserId()。解決方法是在發(fā)布、編輯、刪除等需要登錄的Servlet里先判斷session中user是否為空為空則重定向到登錄頁(yè)。更好的做法是加一個(gè)LoginFilter統(tǒng)一攔截/admin/*路徑。5. 從能跑到能答辯三個(gè)讓論文和項(xiàng)目都加分的進(jìn)階技巧5.1 用Druid監(jiān)控面板證明你懂連接池調(diào)優(yōu)很多同學(xué)的論文里寫“使用了連接池”但答辯時(shí)被問“連接池參數(shù)怎么設(shè)的”就卡住了。Druid自帶一個(gè)監(jiān)控頁(yè)面配置好后可以實(shí)時(shí)看到活躍連接數(shù)、SQL執(zhí)行時(shí)間、慢查詢記錄。在web.xml里加Druid的StatViewServlet訪問/druid/index.html就能看到。論文里可以放一張監(jiān)控截圖說明initialSize設(shè)為5、maxActive設(shè)為20的依據(jù)是“開發(fā)環(huán)境并發(fā)用戶約10人按每人1-2個(gè)連接估算”。這比空寫“使用了連接池”有說服力得多。servlet servlet-nameDruidStatView/servlet-name servlet-classcom.alibaba.druid.support.http.StatViewServlet/servlet-class init-param param-nameloginUsername/param-name param-valueadmin/param-value /init-param init-param param-nameloginPassword/param-name param-valueadmin123/param-value /init-param /servlet servlet-mapping servlet-nameDruidStatView/servlet-name url-pattern/druid/*/url-pattern /servlet-mapping5.2 論文里怎么寫出有信息量的“系統(tǒng)測(cè)試”章節(jié)畢業(yè)論文的系統(tǒng)測(cè)試章節(jié)常見寫法是“點(diǎn)擊按鈕功能正?!薄_@種寫法沒有信息量。建議改成表格形式列出測(cè)試項(xiàng)、輸入數(shù)據(jù)、預(yù)期結(jié)果、實(shí)際結(jié)果、是否通過。比如分頁(yè)查詢測(cè)試輸入pageNum1、pageSize10預(yù)期返回前10條已發(fā)布新聞實(shí)際返回10條且按發(fā)布時(shí)間倒序通過。再比如邊界測(cè)試輸入pageNum0預(yù)期自動(dòng)修正為1實(shí)際返回第1頁(yè)數(shù)據(jù)通過。這樣寫既展示了測(cè)試思維也留下了可復(fù)現(xiàn)的記錄。測(cè)試項(xiàng)輸入預(yù)期結(jié)果實(shí)際結(jié)果結(jié)論分頁(yè)首頁(yè)pageNum1, pageSize10返回10條已發(fā)布新聞返回10條按時(shí)間倒序通過分頁(yè)越界pageNum999自動(dòng)修正為最后一頁(yè)返回最后一頁(yè)數(shù)據(jù)通過空搜索keyword返回全部已發(fā)布新聞返回全部通過XSS注入content含script標(biāo)簽script標(biāo)簽被過濾僅保留p標(biāo)簽通過5.3 源代碼和SQL交付前的自查清單交付前跑一遍這個(gè)清單能避免大部分“在我電腦上能跑”的尷尬。第一把數(shù)據(jù)庫(kù)導(dǎo)出成SQL文件時(shí)確認(rèn)包含建表語(yǔ)句和初始數(shù)據(jù)字符集是utf8mb4。第二檢查代碼里有沒有硬編碼的數(shù)據(jù)庫(kù)密碼有的話抽到配置文件里。第三確認(rèn)web.xml或注解里的URL映射沒有沖突。第四把Tomcat的context path和代碼里的重定向路徑對(duì)齊避免部署后404。第五如果論文里引用了參考文獻(xiàn)格式按學(xué)校要求統(tǒng)一不要混用GB/T 7714和APA。我自己帶學(xué)生做畢設(shè)時(shí)最常提醒的一句話是不要等到答辯前三天才第一次完整部署。提前兩周在另一臺(tái)電腦上從零跑一遍把遇到的每個(gè)報(bào)錯(cuò)記下來這些就是你論文“遇到的問題與解決方案”章節(jié)最好的素材。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取