同過濾:Java電影推薦系統(tǒng)源碼解析與部署指南)
簡(jiǎn)介基于SSM與Vue實(shí)現(xiàn)的一套電影推薦系統(tǒng)源碼面向需要完成課程設(shè)計(jì)、畢業(yè)設(shè)計(jì)或入門SSM前后端分離項(xiàng)目的開發(fā)者涉及Spring、SpringMVC、MyBatisPlus、MySQL、Maven等多個(gè)主流技術(shù)框架。項(xiàng)目圍繞用戶信息、圖片素材與視頻素材等核心模塊完整覆蓋從數(shù)據(jù)庫(kù)設(shè)計(jì)、數(shù)據(jù)接口實(shí)現(xiàn)到前端頁(yè)面展示的流程并從可行性分析、系統(tǒng)設(shè)計(jì)到實(shí)際編碼均有體現(xiàn)可作為電影推薦管理系統(tǒng)的直接參考方案。壓縮包共845個(gè)文件、約17.62MB以Java后端代碼、Vue組件、JS腳本、HTML頁(yè)面和CSS樣式為主另含SQL數(shù)據(jù)庫(kù)腳本、Maven配置及環(huán)境啟動(dòng)腳本導(dǎo)入Eclipse或IDEA配合MySQL5.7即可運(yùn)行調(diào)試。目前已有282人學(xué)習(xí)包內(nèi)同時(shí)提供環(huán)境構(gòu)建腳本、設(shè)計(jì)文檔和項(xiàng)目說明可幫助快速梳理用戶管理、電影信息維護(hù)、素材展示等模塊的開發(fā)思路適合需要一套可直接復(fù)現(xiàn)的SSMVue項(xiàng)目資料。1. 這份電影推薦系統(tǒng)源碼到底能干什么先說結(jié)論這是一個(gè)用 Java SSMSpring SpringMVC MyBatis寫的 Web 電影推薦管理系統(tǒng)前后端不分離JSP 頁(yè)面渲染數(shù)據(jù)庫(kù)用 MySQL。它不是我見過最花哨的項(xiàng)目但它是那種「拿過來能跑、能改、能寫進(jìn)簡(jiǎn)歷、能當(dāng)畢設(shè)」的典型代表。你拿到的資源里包含完整源碼、SQL 建表腳本和部署說明。系統(tǒng)核心不是簡(jiǎn)單做 CRUD而是實(shí)現(xiàn)了一套基于物品的協(xié)同過濾推薦引擎用戶登錄、用戶對(duì)電影打分之后系統(tǒng)會(huì)基于「喜歡電影 A 的人也喜歡電影 B」的邏輯算出相似電影列表并推給當(dāng)前用戶。除了推薦模塊后臺(tái)還有電影管理、用戶管理、評(píng)分管理、輪播圖管理這些常規(guī)功能。什么人適合下載如果你正在找 Java Web 課設(shè)或畢設(shè)題目或者想研究 SSM 整合和協(xié)同過濾推薦在實(shí)際項(xiàng)目里怎么落地這份源碼是很好的參照物。它不涉及 Spring Boot用的是偏傳統(tǒng)的 SSM 配置方式恰恰是很多課程還在教、面試還在問的那一套。下面我按「算法與數(shù)據(jù)怎么設(shè)計(jì) → 部署怎么做 → 坑在哪 → 怎么改得更好」的順序把這套系統(tǒng)完整拆一遍你照著走完就能跑起來。2. 推薦算法與數(shù)據(jù)模型基于物品的協(xié)同過濾選型與五張表設(shè)計(jì)這部分我先講算法為什么選型再落到數(shù)據(jù)庫(kù)表和代碼實(shí)現(xiàn)。推薦模塊是這套系統(tǒng)的靈魂如果只是增刪改查那它和普通管理系統(tǒng)的區(qū)別就沒了如果你想把推薦邏輯改掉第一步得先把數(shù)據(jù)模型吃透。2.1 為什么選基于物品的協(xié)同過濾而不是基于用戶推薦系統(tǒng)最常見的兩個(gè)方向是按用戶相似度推薦UserCF和按物品相似度推薦ItemCF。這套源碼選的是 ItemCF也就是基于物品的協(xié)同過濾。我傾向于認(rèn)為這是對(duì)的尤其是對(duì)電影這類物品數(shù)量相對(duì)穩(wěn)定、用戶數(shù)量會(huì)持續(xù)增長(zhǎng)的業(yè)務(wù)模型。ItemCF 的核心思想是先找用戶和歷史行為的關(guān)系算出電影之間的相似度再根據(jù)用戶已經(jīng)看過的電影去推薦相似電影。它和 UserCF 的區(qū)別在于UserCF 是要找到和你品味相似的人把這個(gè)人看過的而你沒看過的電影推薦給你ItemCF 則是找到你已看過的電影的相似品推薦給用戶。為什么更適合電影網(wǎng)站因?yàn)橛脩舻呐d趣變化相對(duì)慢而電影的「鄰居關(guān)系」是比較穩(wěn)定的。再加上電影這種物品的用戶行為矩陣很稀疏絕大多數(shù)用戶只看過幾百部電影中的幾十部UserCF 計(jì)算用戶相似度時(shí)矩陣太稀疏結(jié)果不穩(wěn)定ItemCF 只需要為目標(biāo)電影找 Top-K 個(gè)相似電影這個(gè)計(jì)算是離線可以預(yù)處理的在線推薦時(shí)只需查表即可。這套系統(tǒng)的實(shí)現(xiàn)方式是在 Java 服務(wù)啟動(dòng)時(shí)加載評(píng)分?jǐn)?shù)據(jù)內(nèi)存里直接算好每個(gè)電影的相似列表存入緩存。用戶請(qǐng)求推薦接口時(shí)拉取用戶自己的評(píng)分記錄將這些電影的各相似電影按相似度加權(quán)求和排序后返回 Top-N。2.2 數(shù)據(jù)庫(kù)表五張基礎(chǔ)表覆蓋業(yè)務(wù)閉環(huán)這套系統(tǒng)跑起來之后MySQL 里會(huì)自動(dòng)建出的核心表是五張我建議你拿到源碼先看一遍 SQL 腳本對(duì)字段千萬(wàn)別懶著不看。它們的職責(zé)分布是表名職責(zé)關(guān)鍵字段user用戶表id, username, password, nickname, avatar, typemovie電影表id, title, cover, director, actors, description, publish_date, scorerating評(píng)分表id, user_id, movie_id, rating_score, create_timecategory或 type電影分類表id, namenotice / carousel公告或輪播圖表id, content, image_url, create_time需要注意 rating 表是推薦算法的主數(shù)據(jù)源。它存儲(chǔ)的是用戶對(duì)某部電影的打分字段名字可能在不同版本里叫 score 或 rating_score但邏輯一樣user_id 和 movie_id 聯(lián)合起來不能重復(fù)這約束了同一用戶對(duì)同一電影只能有一個(gè)評(píng)分。movie 表里的 score 字段不是用戶的評(píng)分而是電影自身的綜合評(píng)分后臺(tái)管理員錄入的初始值或者系統(tǒng)加權(quán)計(jì)算出的展示分。這個(gè)字段很容易混淆很多初學(xué)者會(huì)直接把 movie.score 當(dāng)成用戶評(píng)分傳給推薦算法導(dǎo)致相似度計(jì)算完全失真。后面避坑章節(jié)我會(huì)再?gòu)?qiáng)調(diào)一次。2.3 相似度計(jì)算余弦相似度的 Java 實(shí)現(xiàn)與參數(shù)說明代碼包里和推薦算法相關(guān)的核心類通常是RecommendService或ItemCFService我按這套系統(tǒng)最可能的實(shí)現(xiàn)方式給你拆解。以下是基于物品的協(xié)同過濾中計(jì)算電影相似度的核心代碼// 根據(jù)用戶評(píng)分?jǐn)?shù)據(jù)構(gòu)建“電影-用戶”倒排表 public MapInteger, ListInteger buildItemUserMap() { // key: movieId, value: 對(duì)該電影評(píng)過分的userId列表 MapInteger, ListInteger itemUserMap new HashMap(); ListRating ratings ratingMapper.selectAll(); for (Rating r : ratings) { itemUserMap.computeIfAbsent(r.getMovieId(), k - new ArrayList()).add(r.getUserId()); } return itemUserMap; } public MapInteger, ListSimilarMovie calcSimilarity() { MapInteger, ListInteger itemUserMap buildItemUserMap(); ListInteger movieIds new ArrayList(itemUserMap.keySet()); MapInteger, ListSimilarMovie simMap new HashMap(); for (int i 0; i movieIds.size(); i) { ListInteger usersA itemUserMap.get(movieIds.get(i)); for (int j i 1; j movieIds.size(); j) { ListInteger usersB itemUserMap.get(movieIds.get(j)); // 取兩個(gè)電影評(píng)分用戶集合的交集 SetInteger intersection new HashSet(usersA); intersection.retainAll(usersB); if (intersection.size() 2) { continue; // 共同評(píng)分的用戶太少相似度沒有統(tǒng)計(jì)意義 } double sim cosineSimilarity(usersA, usersB); // 雙向?qū)懭胂嗨脐P(guān)系只保留Top-K控制內(nèi)存占用 simMap.computeIfAbsent(movieIds.get(i), k - new ArrayList()) .add(new SimilarMovie(movieIds.get(j), sim)); simMap.computeIfAbsent(movieIds.get(j), k - new ArrayList()) .add(new SimilarMovie(movieIds.get(i), sim)); } } // 每部電影的相似列表按相似度降序截?cái)酁門OP_N simMap.values().forEach(list - { list.sort((a, b) - Double.compare(b.getSimilarity(), a.getSimilarity())); if (list.size() TOP_N) list.subList(TOP_N, list.size()).clear(); }); return simMap; }這段代碼有兩個(gè)關(guān)鍵參數(shù)。TOP_N控制相似列表保留多少個(gè)鄰居默認(rèn) 10 到 20 之間為好。設(shè)太小推薦結(jié)果的多樣性不夠設(shè)太大會(huì)讓后續(xù)的加權(quán)聚合計(jì)算變慢而且相似度排名靠后的電影貢獻(xiàn)很小。intersection.size() 2這個(gè)過濾條件是常見的工程處理如果兩部電影只有一個(gè)共同評(píng)分用戶那說明兩個(gè)電影之間并沒有穩(wěn)定的「共同偏好」支撐保留這個(gè)相似度會(huì)引入大量噪聲。2.4 推薦結(jié)果生成根據(jù)用戶評(píng)分加權(quán)聚合相似度算完推薦的第二步是把用戶評(píng)分過的電影作為「種子」聚合它們的相似電影得到最終推薦列表。核心邏輯是用戶給電影 A 打了 5 分A 和電影 B 的相似度是 0.8那么 B 獲得一個(gè)候選分 5 × 0.8 4.0。public ListMovie recommendForUser(Integer userId, int topN) { // 1. 拉取該用戶的歷史評(píng)分評(píng)分3分的電影作為種子 ListRating userRatings ratingMapper.selectByUserId(userId); MapInteger, Double seedMovieScores new HashMap(); for (Rating r : userRatings) { if (r.getRatingScore() 3.0) { seedMovieScores.put(r.getMovieId(), r.getRatingScore()); } } if (seedMovieScores.isEmpty()) { // 冷啟動(dòng)無(wú)評(píng)分時(shí)退化為返回?zé)岫劝癖苊饬斜砜斩?return movieMapper.selectHotMovies(topN); } // 2. 聚合候選電影 所有種子電影相似列表的并集 MapInteger, Double candidateScoreMap new HashMap(); for (Map.EntryInteger, Double seed : seedMovieScores.entrySet()) { ListSimilarMovie similarList similarityCache.get(seed.getKey()); if (similarList null) continue; for (SimilarMovie sm : similarList) { if (seedMovieScores.containsKey(sm.getMovieId())) { continue; // 過濾掉用戶已經(jīng)看過的 } double rawScore sm.getSimilarity() * seed.getValue(); // 同一個(gè)電影來自多個(gè)種子的得分做累加 candidateScoreMap.merge(sm.getMovieId(), rawScore, Double::sum); } } // 3. 排序并截?cái)?return candidateScoreMap.entrySet().stream() .sorted((a, b) - Double.compare(b.getValue(), a.getValue())) .limit(topN) .map(entry - movieMapper.selectById(entry.getKey())) .collect(Collectors.toList()); }這里需要注意過濾已看過的電影這步必須在聚合之后、排序之前做否則種子電影自己會(huì)排到榜首。閾值選 3.0 是我調(diào)試時(shí)常用的默認(rèn)值低于 3 分的電影通常意味著用戶不喜歡把它作為推薦種子反而會(huì)推薦出用戶討厭的類型。給用戶的推薦頁(yè)可以同時(shí)展示三個(gè)列表猜你喜歡、相似電影、熱門榜單前兩個(gè)來自這套算法高情商的頁(yè)面設(shè)計(jì)會(huì)把沒有評(píng)分的新注冊(cè)用戶先引導(dǎo)到熱門榜上而不是讓他對(duì)著空列表發(fā)呆。3. 從源碼到運(yùn)行環(huán)境要求、建庫(kù)配置與啟動(dòng)鏈路源碼能下載是一回事能跑起來是另一回事。這個(gè)項(xiàng)目對(duì)新手來說存在一些不動(dòng)手根本發(fā)現(xiàn)不了的隱性要求。我先按照「環(huán)境 → 建庫(kù) → 改配置 → 啟動(dòng)」這條鏈路把完整流程走一遍再單獨(dú)把啟動(dòng)過程中常見的故障點(diǎn)集中講透。3.1 環(huán)境版本組合JDK 8 Tomcat 8.5 MySQL 5.7我先說版本組合這是因?yàn)?SSM 項(xiàng)目對(duì)版本非常敏感。這套源碼是 Maven 工程pom.xml 里的依賴版本大概鎖定在比較保守的區(qū)間Spring 4.x 或 5.x、MyBatis 3.4 左右。我建議你用下面的組合去準(zhǔn)備環(huán)境組件版本理由JDK1.8SSM 項(xiàng)目原生基于 JDK 8 編譯用 JDK 11 可能出現(xiàn)反射或字節(jié)碼增強(qiáng)問題Tomcat8.5支持 Servlet 3.1兼容項(xiàng)目 web.xml 配置Tomcat 9 對(duì)部分舊配置更嚴(yán)格MySQL5.7與 MyBatis 的 SQL 寫法兼容性最好8.0 的認(rèn)證插件會(huì)導(dǎo)致驅(qū)動(dòng)連接失敗Maven3.6構(gòu)建工具版本過老可能拉不到依賴IntelliJ IDEA2020配置 Tomcat 和部署 war 包更方便有兩點(diǎn)容易踩。MySQL 8.0 是重災(zāi)區(qū)如果你用 8.0必須把驅(qū)動(dòng)換成mysql-connector-java 8.0.x并且 url 后面要加useSSLfalseserverTimezoneAsia/Shanghai。T8.0 的 caching_sha2_password 認(rèn)證插件是舊版驅(qū)動(dòng)不認(rèn)的。另外 Tomcat 版本如果高于 9JSP 編譯依賴的 javax.servlet 包名會(huì)有兼容性變化項(xiàng)目里舊的 EL 表達(dá)式寫法可能直接取不到值。3.2 建庫(kù)導(dǎo)入 SQL 腳本三條命令搞定初始化源碼包里應(yīng)該包含一個(gè)movie.sql或db_movie.sql腳本文件。用下面的方式導(dǎo)入即可# 登錄本地MySQLroot用戶 mysql -u root -p # 在MySQL命令行中執(zhí)行 CREATE DATABASE IF NOT EXISTS movie_db DEFAULT CHARACTER SET utf8mb4; USE movie_db; SOURCE /your/path/movie.sql; # 查看是否導(dǎo)入成功 SHOW TABLES;這里SOURCE的路徑必須用正斜杠Windows 下也如此否則 MySQL 會(huì)報(bào)語(yǔ)法錯(cuò)誤。utf8mb4這個(gè)字符集不是可選項(xiàng)——如果表結(jié)構(gòu)里原本是 utf8遇到 emoji 或冷門中文名稱會(huì)存入失敗。導(dǎo)入完成后重點(diǎn)確認(rèn) rating 表里有數(shù)據(jù)或者執(zhí)行一條統(tǒng)計(jì) SQL 查看各表行數(shù)如果發(fā)現(xiàn) user 表有數(shù)據(jù)但 rating 表是空的推薦接口是調(diào)不出結(jié)果的后面我會(huì)解釋這是最常見的一個(gè)翻車點(diǎn)。3.3 修改配置jdbc.properties 與 Maven 依賴接著打開src/main/resources/jdbc.properties數(shù)據(jù)庫(kù)連接配置改成你自己的本地環(huán)境jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/movie_db?useUnicodetruecharacterEncodingutf-8useSSLfalse jdbc.usernameroot jdbc.passwordyourpassword這里驅(qū)動(dòng)類名值得說一句。如果用的是 MySQL 5.7 mysql-connector-java 5.1.x驅(qū)動(dòng)類名是com.mysql.jdbc.Driver如果換了 MySQL 8.0 8.0 驅(qū)動(dòng)必須改成com.mysql.cj.jdbc.Driver否則啟動(dòng)日志會(huì)給出ClassNotFoundException。characterEncodingutf-8是必填參數(shù)不寫的話亂碼問題幾乎百分百出現(xiàn)而且亂碼會(huì)貫穿從 JSP 頁(yè)面到數(shù)據(jù)庫(kù)的全鏈路。pom.xml 里如果缺少某項(xiàng)依賴Maven 會(huì)在編譯時(shí)報(bào)錯(cuò)。建議先執(zhí)行一次mvn clean package -DskipTests確認(rèn)所有依賴能順利拉取這樣可以把「代碼問題」和「環(huán)境問題」分開排查。如果項(xiàng)目里有本地lib目錄的 jar 包記得確認(rèn) IDE 是否正確引用了這是很多從網(wǎng)盤下載的源碼里被忽略的問題——構(gòu)建時(shí) Maven 報(bào)包找不到實(shí)際上 jar 就放在 lib 下。3.4 部署啟動(dòng)IDEA 配置 Tomcat 的完整步驟我不在這里展開 IDEA 的每個(gè)點(diǎn)擊步驟只把最關(guān)鍵的三步寫清楚。第一步Project Structure 里把 Artifacts 設(shè)為 war exploded 模式而不是 war 包模式war exploded 允許熱部署 JSP 和 Java 文件修改代碼后不用反復(fù)重啟整個(gè)項(xiàng)目。第二步Run Configuration 里添加 Tomcat Server LocalDeployment 面板把項(xiàng)目 artifact 部署上去Application context 建議設(shè)為/movie這樣可以避免根路徑下的靜態(tài)資源沖突。第三步啟動(dòng)前確認(rèn) Tomcat 的 VM options 里沒設(shè)置奇怪的-Dfile.encoding否則 JSP 中文輸出可能有隱藏的編碼坑。啟動(dòng)成功后瀏覽器訪問http://localhost:8080/movie。到這里系統(tǒng)的登錄、注冊(cè)、電影列表、電影詳情、評(píng)分功能應(yīng)該全部可用。如果首頁(yè)能打開但推薦列表為空問題幾乎都出在數(shù)據(jù)或算法觸發(fā)條件上這是下一章要細(xì)講的內(nèi)容。3.5 推薦鏈路驗(yàn)證怎樣確認(rèn)推薦真的在生效服務(wù)起來以后想要確認(rèn)推薦模塊真的在工作有一個(gè)簡(jiǎn)單的驗(yàn)證路徑。注冊(cè)一個(gè)新用戶給三五部電影打高分然后打開「猜你喜歡」頁(yè)面。如果頁(yè)面上出現(xiàn)了你沒打過分、但和你打過分的電影類型相近的電影說明推薦鏈路是通的。更好的是直接查日志。推薦接口的 Service 通常會(huì)打印用戶種子電影數(shù)和候選電影數(shù)看到seed3 candidate45這類信息就能確定算法層的輸入輸出正常。如果打了分但日志里seed0一定是評(píng)分?jǐn)?shù)據(jù)沒有被 Service 拿到「查一下是不是用錯(cuò)了 mapper 方法」是這類問題最直接的排查方向。4. 避坑指南四類高頻問題與排查路徑這一章寫的是我在反復(fù)部署這類 SSM 源碼項(xiàng)目時(shí)踩過的坑每條都按「現(xiàn)象 → 原因 → 解決」來講。你對(duì)照著排能省下大量瞎試時(shí)間。4.1 登錄后推薦頁(yè)空白或 500 報(bào)錯(cuò)現(xiàn)象首頁(yè)能打開登錄也正常但進(jìn)入推薦頁(yè)直接白屏后端日志報(bào) NullPointerException指向 RecommendService 里的某一行業(yè)務(wù)代碼。原因推薦模塊的相似度計(jì)算在啟動(dòng)時(shí)沒有成功完成或者說緩存的similarityCache是空的。常見誘因有兩個(gè)一是 rating 表數(shù)據(jù)為空算法沒有輸入自然沒法產(chǎn)出二是 MovieMapper 的查詢字段名和實(shí)體類屬性沒對(duì)應(yīng)上比如數(shù)據(jù)庫(kù)字段是publish_date實(shí)體里是publishDateMyBatis 默認(rèn)自動(dòng)映射下駝峰轉(zhuǎn)下劃線沒啟用導(dǎo)致查出來的對(duì)象是半空的。解決先查數(shù)據(jù)庫(kù)確認(rèn) rating 表有數(shù)據(jù)SELECT COUNT(*) FROM rating;。然后檢查 mybatis-config.xml 里是否配置了setting namemapUnderscoreToCamelCase valuetrue/。沒配置的話要么加這個(gè)配置要么在 SQL 里顯式使用別名比如SELECT publish_date AS publishDate FROM movie。4.2 注冊(cè)用戶后推薦列表全是熱門電影而非個(gè)性化推薦現(xiàn)象無(wú)論哪個(gè)用戶登錄推薦頁(yè)展示的都是同一批電影看起來就是按熱度排的。原因這套系統(tǒng)的推薦設(shè)計(jì)里有冷啟動(dòng)退化邏輯當(dāng)用戶沒有評(píng)分時(shí)返回?zé)衢T榜。如果所有用戶看到的東西都一樣而且你確認(rèn)這些用戶明明打過評(píng)分那要檢查推薦入口是否真的調(diào)用了推薦接口。有時(shí)頁(yè)面直接渲染了默認(rèn)列表熱門榜推薦接口根本沒有被調(diào)用。解決看瀏覽器的 Network 面板或后端日志確認(rèn)「猜你喜歡」板塊的 Ajax 請(qǐng)求是否發(fā)到了recommend接口。如果發(fā)到了但返回內(nèi)容仍是熱門榜就查用戶評(píng)分是否真正寫入了數(shù)據(jù)庫(kù)——HTTP 200 不等于事務(wù)已提交檢查評(píng)分表單提交后是否調(diào)用了 ratingMapper.insert 并確認(rèn)userId是當(dāng)前登錄用戶而不是默認(rèn)的 1。4.3 中文亂碼頁(yè)面亂碼和數(shù)據(jù)庫(kù)亂碼同時(shí)出現(xiàn)現(xiàn)象電影標(biāo)題在頁(yè)面上顯示為問號(hào)或方格MySQL 里存進(jìn)去的中文也是亂碼。原因三層編碼不一致。JSP 頁(yè)面沒指定 UTF-8Tomcat 接收 POST 請(qǐng)求時(shí) URIEncoding 沒配置數(shù)據(jù)庫(kù)連接的 characterEncoding 沒加。解決JSP 頂部加上% page contentTypetext/html;charsetUTF-8 languagejava %MySQL 的 url 加characterEncodingutf-8Tomcat 的 server.xml 里 Connector 配置加上URIEncodingUTF-8。這三處都改好后重啟 Tomcat把表的字符集也統(tǒng)一用ALTER TABLE movie CONVERT TO CHARACTER SET utf8mb4;這樣比較穩(wěn)妥。4.4 MySQL 8.0 連接失敗Access denied 或 Public Key Retrieval 錯(cuò)誤現(xiàn)象項(xiàng)目啟動(dòng)時(shí)提示Access denied for user rootlocalhost或者連接 MySQL 時(shí)報(bào)Public Key Retrieval is not allowed。原因MySQL 8.0 默認(rèn)的認(rèn)證插件是 caching_sha2_password舊版的 5.x 驅(qū)動(dòng)不認(rèn)識(shí)。如果 root 密碼是空或者用了老密碼也會(huì)導(dǎo)致相同報(bào)錯(cuò)。解決兩個(gè)方案任選。方案一把 pom.xml 里的 mysql-connector-java 版本換成 8.0.xurl 加allowPublicKeyRetrievaltrueuseSSLfalse。方案二在 MySQL 里把 root 用戶的認(rèn)證插件改回 mysql_native_passwordALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY yourpassword;。我建議方案一因?yàn)楦牟寮?huì)有其他兼容性隱患而且 8.0 驅(qū)動(dòng)是向前兼容 5.7 的。5. 再進(jìn)一步加上時(shí)間衰減和用戶行為權(quán)重讓推薦列表真正變可用基礎(chǔ)版協(xié)同過濾能跑通是一回事但如果你把這份源碼當(dāng)成畢業(yè)設(shè)計(jì)或上線項(xiàng)目去打磨你會(huì)發(fā)現(xiàn)還有三個(gè)值得優(yōu)化的方向。它們是推薦系統(tǒng)從「能演示」走向「可用」的分水嶺我把具體做法和代碼給你寫出來。5.1 時(shí)間衰減讓三個(gè)月前的評(píng)分和昨天的評(píng)分權(quán)重不同基礎(chǔ)版算法里用戶的每個(gè)評(píng)分權(quán)重是完全一樣的。實(shí)際上三個(gè)月前的評(píng)分和昨天的評(píng)分對(duì)用戶當(dāng)前興趣的反映程度是不同的所以推薦結(jié)果里容易出現(xiàn)「舊片堆砌」的問題——用戶看過的老電影和相似老電影長(zhǎng)期霸榜新上架的電影永遠(yuǎn)沒機(jī)會(huì)被推薦。常見做法是給評(píng)分加一個(gè)時(shí)間衰減因子比如衰減權(quán)重decay Math.pow(0.95, daysFromToday / 7.0)表示每過一周權(quán)重降低 5%。把算好的權(quán)重乘到種子評(píng)分的 value 上去。實(shí)現(xiàn)非常簡(jiǎn)單核心代碼不超過十行。在recommendForUser方法里拿到 Rating 對(duì)象后多讀一個(gè)createTime字段計(jì)算天數(shù)差然后在聚合時(shí)把 rawScore 乘上衰減因子。這樣同比兩部電影近期評(píng)分貢獻(xiàn)的推薦分更高推薦列表會(huì)跟隨用戶近期口味變化而不是永遠(yuǎn)停留在注冊(cè)頭一個(gè)月的觀影記錄上。配合這個(gè)改動(dòng)數(shù)據(jù)庫(kù)里 publish_date 字段就有了實(shí)用價(jià)值電影相似度相同或接近時(shí)讓發(fā)布時(shí)間更新的電影排名靠前這也是解決冷門新片很難被發(fā)現(xiàn)的標(biāo)配手法。ORDER BY publish_date DESC不是簡(jiǎn)單地在最后排一下而是把它以很小的權(quán)重比如 0.01 系數(shù)加進(jìn)排序分?jǐn)?shù)否則會(huì)讓相似度計(jì)算失去主導(dǎo)權(quán)。5.2 行為權(quán)重?cái)U(kuò)展不只依賴評(píng)分把收藏和瀏覽也納入用戶偏好這套系統(tǒng)有評(píng)分接口但通常還有收藏或想看功能。如果你有收藏表或?yàn)g覽歷史表可以將它們轉(zhuǎn)成隱性反饋不需要用戶主動(dòng)給分。單次瀏覽算 0.5 分收藏算 2 分評(píng)分算實(shí)際分?jǐn)?shù)。這樣即使用戶只是隨便逛逛、沒打幾分也能積累足夠的行為種子。尤其是新注冊(cè)用戶讓他一上來先看到熱門榜收藏兩三部動(dòng)作片之后立刻就能看到動(dòng)作片方向的推薦這個(gè)體驗(yàn)遠(yuǎn)勝于等用戶打完 5 部電影才出結(jié)果。5.3 評(píng)測(cè)方法離線切分評(píng)測(cè)集量化推薦效果很多畢業(yè)設(shè)計(jì)只講到「推薦出來了」就停了面試官一問「效果好不好」就答不上來。這個(gè)問題推薦系統(tǒng)有標(biāo)準(zhǔn)解法離線評(píng)測(cè)。把用戶評(píng)分按時(shí)間排序前 80% 當(dāng)訓(xùn)練集后 20% 當(dāng)測(cè)試集。用訓(xùn)練集數(shù)據(jù)重新計(jì)算相似度和預(yù)測(cè)分看預(yù)測(cè)的 Top-N 電影里有多少出現(xiàn)在測(cè)試集的實(shí)際評(píng)分記錄里。-- 按用戶分組按時(shí)間排序把最后一條評(píng)分作為測(cè)試集 -- 訓(xùn)練數(shù)據(jù)該用戶其他評(píng)分為訓(xùn)練集 SELECT user_id FROM rating GROUP BY user_id HAVING COUNT(*) 5;這種切分方式比隨機(jī)切分更接近真實(shí)場(chǎng)景因?yàn)樗前磿r(shí)間前后模擬預(yù)測(cè)未來行為。算出準(zhǔn)確率PrecisionN和召回率RecallN就能成為論文里的核心數(shù)據(jù)指標(biāo)。調(diào)參時(shí)重點(diǎn)看兩個(gè)方向TOP_N值設(shè)到多少時(shí)準(zhǔn)確率下降、覆蓋率上升以及時(shí)間衰減系數(shù)設(shè)成 0.95 還是 0.90 時(shí)推薦列表里的新片占比是否有明顯差異。這組實(shí)驗(yàn)數(shù)據(jù)拿出來和你簡(jiǎn)歷上的「實(shí)現(xiàn)推薦系統(tǒng)」相比說服力好得多。5.4 一個(gè)讓推薦列表更像產(chǎn)品的小技巧最后說一個(gè)改動(dòng)量很小但體驗(yàn)提升很大的點(diǎn)在推薦結(jié)果的渲染層去重和打散。算相似度時(shí)我已經(jīng)做了 Top-K 截?cái)嗟嗦吠扑]合并猜你喜歡 相似電影 熱門榜會(huì)出現(xiàn)同一部電影在多個(gè)列表里重復(fù)出現(xiàn)。前端展示時(shí)先把所有候選合并成一個(gè) Map按類型或?qū)а葑鲆淮稳ブ卦僭诮Y(jié)果里插入一定比例的隨機(jī)熱門電影。這樣可以避免推薦列表里的前幾條全是同一系列的電影——否則用戶會(huì)覺得系統(tǒng)根本沒在推薦只是把自己看過的系列又列了一遍。從那以后我每次部署完推薦系統(tǒng)都要強(qiáng)制走一遍三件事先看數(shù)據(jù)庫(kù)評(píng)分?jǐn)?shù)據(jù)分布確認(rèn)種子不是全空的再人為構(gòu)造一個(gè)「只看過動(dòng)作片」的用戶來驗(yàn)證個(gè)性化方向的區(qū)分度最后把推薦接口掛上日志跑一周看看實(shí)際推薦為空的次數(shù)占多大比例。這套驗(yàn)證習(xí)慣能幫你早點(diǎn)發(fā)現(xiàn)問題而不是上線后讓用戶自己暴露問題。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取