简介这是一份基于Spring Boot与Vue的新闻推荐系统毕业设计资源包内含可运行源码与完整论文面向计算机专业学生以及需要搭建推荐类前后端项目的开发者。系统按管理员和用户双端设计实现了用户信息管理、新闻资讯发布、排行榜维护、我的收藏等典型业务模块论文从研究背景、系统分析、数据库设计到系统测试均有完整论述可直接作为课程设计或毕业设计的参考方案。压缩包共748个文件压缩后约15.15MB以Java、Vue、JavaScript和CSS等源码文件为主另含SQL数据库脚本、启动脚本、Maven构建文件及多种配置能够帮助读者快速还原环境并部署运行。目前已有34人学习下载。使用者可获得一套新闻推荐系统源码、完整论文文档、数据库建表脚本以及从环境准备、代码调试到系统测试的整套落地思路便于复用代码、理解系统架构或用于答辩准备。1. 为什么「基于SpringBoot的新闻推荐系统」值得自己从头写一遍刷到不少人在找「基于springboot新闻推荐系统设计与实现.7z源码论文」这类压缩包其实这套东西在Java课程设计和毕业设计里出现频率极高但它不是简单的CRUD。拆开看它至少包含三块硬骨头新闻内容的分类打标、用户行为的采集与存储、以及推荐算法的工程化落地。三块叠加才让这个题目既够得着「设计实现」的深度要求又不会难到没法收尾。这套系统适合两类人一是需要交毕设或课程设计的学生想要一个能跑通、能讲清、能答辩的项目二是工作中想快速搭一个内容分发demo的后端开发想看看推荐逻辑怎么和SpringBoot生态融合。后者尤其要注意实际生产里的推荐系统比毕设复杂得多但这个项目的价值在于把「推荐」从算法题变成工程问题——数据表怎么建、日志怎么埋、算法怎么调参、效果怎么证明每一步都有明确答案。我按自己做内容系统的经验把这条线完整走一遍从需求拆解到表结构设计从核心算法实现到参数调优再到避坑和验证。中间给的代码不是某个神秘源码包里的片段而是我常用的可复现写法可以直接抄进你的项目里改造。2. 需求拆解与核心表结构三张表撑起一个推荐闭环2.1 先把「推荐」拆成三个能落地的子问题新闻推荐系统看着是个整体真正做设计时我会拆成三个独立子问题给新闻分类打标签、记录用户读了什么、根据阅读历史算相似。这三个问题对应后端三块职责也对应数据库里三类核心表。第一块是新闻内容表负责存储新闻正文、分类、发布时间、来源。第二块是用户行为表记录每一次点击、浏览时长、是否点赞或收藏。第三块是用户画像表存储系统对每个用户的兴趣建模结果通常是「用户ID 标签权重JSON」这种结构。很多新手只建前两张表推荐时现算历史记录导致查询越来越慢。我一般会加第三张表定时任务把用户行为聚合成兴趣向量存进去查询时直接读聚合结果。还有个常被忽略的设计决策行为日志要不要用消息队列。毕设项目、中小型demo不需要引入RabbitMQ或Kafka直接在service层写个异步方法落库就够只有在用户量到达一定量级、日志写入开始拖慢主流程时才考虑异步削峰。这个判断很重要能帮你省掉大量不必要的中间件配置。2.2 新闻表与行为表的具体建表SQL直接给出可复用的表结构这是整个系统的基础后面所有算法都依赖这几张表。先建新闻表CREATE TABLE news ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 新闻ID, title varchar(255) NOT NULL COMMENT 新闻标题, content longtext NOT NULL COMMENT 正文内容, category varchar(50) DEFAULT 未分类 COMMENT 分类科技/财经/体育等, tags varchar(255) DEFAULT NULL COMMENT 人工标签逗号分隔, source varchar(100) DEFAULT NULL COMMENT 来源, publish_time datetime DEFAULT NULL COMMENT 发布时间, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category), KEY idx_publish_time (publish_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT新闻内容表;说明一下几个设计考量。category字段必须加索引因为冷启动阶段要靠分类做兜底推荐。content用longtext是因为新闻正文经常超过64KB用text类型会有截断风险。tags字段允许为空空的时候靠后面的TF-IDF算法自动提取关键词。publish_time加索引的意义在于推荐时经常要过滤「三天内的新闻」或「一周内的热门」没有索引会全表扫描。再建用户行为表这是推荐系统的数据燃料CREATE TABLE user_behavior ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL COMMENT 用户ID, news_id bigint(20) NOT NULL COMMENT 新闻ID, behavior_type tinyint(4) NOT NULL DEFAULT 1 COMMENT 1-点击 2-浏览超过30秒 3-点赞 4-收藏, behavior_value float DEFAULT 1.0 COMMENT 行为权重点赞2.0收藏3.0, duration int(11) DEFAULT NULL COMMENT 浏览时长秒, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user (user_id, created_at), KEY idx_news (news_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户行为日志表;这里的关键设计是behavior_type和behavior_value分离。类型记录行为本身权重用于推荐算法计算新闻热度。点赞权重设为2.0、收藏设为3.0这么做是因为收藏的行为成本更高代表更强的兴趣信号。duration字段独立存储计算用户兴趣时可以做时间衰减。真实的推荐系统里行为数据量会很大但毕设阶段单表完全够用。2.3 SpringBoot中建实体与Mapper的约定表结构定好后在SpringBoot里用MyBatis-Plus还是JPA是个常见纠结。我推荐MyBatis-Plus理由不是它更先进而是它对新手更友好单表CRUD不用写XML分页插件现成代码生成器能直接把表转成实体、Mapper、Service。实体类直接对应上面的表结构不需要额外逻辑。Mapper接口继承BaseMapperT就能获得基础方法public interface NewsMapper extends BaseMapperNews { // 自定义查询按时间倒序取最新新闻 Select(SELECT * FROM news WHERE publish_time DATE_SUB(NOW(), INTERVAL 3 DAY) ORDER BY view_count DESC LIMIT #{limit}) ListNews selectHotNews(Param(limit) int limit); // 自定义查询按行为权重统计热门新闻 Select(SELECT news_id, SUM(behavior_value) AS hot_score FROM user_behavior WHERE created_at DATE_SUB(NOW(), INTERVAL 7 DAY) GROUP BY news_id ORDER BY hot_score DESC LIMIT #{limit}) ListMapString, Object selectHotByBehavior(Param(limit) int limit); }这段代码需要注意的点Select注解适合简单SQL复杂动态SQL还是建议写在XML里否则可读性会崩。第一个查询用DATE_SUB做时间过滤索引idx_publish_time能派上用场。第二个查询统计近7天行为热度这里我用SUM(behavior_value)而非COUNT(*)因为权重不同单纯计数会把点击和收藏混为一谈。这个热度分数后面冷启动推荐会用到。3. 把推荐算法落进SpringBoot协同过滤与TF-IDF的工程实现3.1 选型为什么用「协同过滤 内容推荐」双通道推荐算法选型是个经常让人纠结的问题。新闻推荐和电商推荐有个明显区别新闻的生命周期极短昨天的热门今天可能就没人在乎所以纯靠用户行为做协同过滤会面临严重的时效性问题但纯靠内容相似度又没法发现用户潜在的新兴趣。常见做法是双通道第一通道是基于用户的协同过滤核心逻辑是「和你兴趣相似的人读了什么也推荐给你」。第二通道是基于内容的推荐核心逻辑是「你读过的新闻里提取关键词去找关键词重合度高的新新闻」。两条通道各自算一个候选集按权重合并再去重。这样做的好处是协同过滤保证「猜你喜欢」的多样性内容推荐保证「时效性」和「冷启动」兜底。实际落地时协同过滤的计算成本高于内容推荐因为要实时算用户间相似度。我的做法是用户相似度用定时任务离线算好存Redis或数据库表用户请求进来时只做查表和排序。这个「离线计算 在线服务」的架构是生产系统的标准姿势也是毕设答辩时能讲出彩的点。3.2 基于用户的协同过滤算相似度矩阵的完整代码先写离线计算用户相似度的核心逻辑。这里选用皮尔逊相关系数因为它在用户评分尺度不一致时比余弦相似度更稳Service public class UserSimilarityService { Autowired private UserBehaviorMapper behaviorMapper; // 计算所有用户之间的相似度结果存入 user_similarity 表 Scheduled(cron 0 0 3 * * ?) // 每天凌晨3点执行 public void calcUserSimilarity() { // 1. 获取近30天有行为的用户列表 ListLong userIds behaviorMapper.selectActiveUserIds(30); int n userIds.size(); // 2. 构建 用户-新闻 评分矩阵行为权重叠加 MapLong, MapLong, Double userNewsMap new HashMap(); for (Long uid : userIds) { ListUserBehavior behaviors behaviorMapper.selectByUserId(uid); MapLong, Double newsScores new HashMap(); for (UserBehavior ub : behaviors) { newsScores.merge(ub.getNewsId(), ub.getBehaviorValue(), Double::sum); } userNewsMap.put(uid, newsScores); } // 3. 两两计算皮尔逊相关系数 for (int i 0; i n; i) { for (int j i 1; j n; j) { Long uid1 userIds.get(i); Long uid2 userIds.get(j); double similarity pearson( userNewsMap.get(uid1), userNewsMap.get(uid2) ); if (similarity 0.3) { // 阈值过滤低相似度 behaviorMapper.insertSimilarity(uid1, uid2, similarity); behaviorMapper.insertSimilarity(uid2, uid1, similarity); } } } } private double pearson(MapLong, Double map1, MapLong, Double map2) { // 取两个用户都评价过的新闻ID集合 SetLong commonKeys new HashSet(map1.keySet()); commonKeys.retainAll(map2.keySet()); if (commonKeys.size() 2) { return 0.0; // 共同评分项太少相似度没有统计意义 } double sum1 0, sum2 0, sum1Sq 0, sum2Sq 0, pSum 0; for (Long key : commonKeys) { double v1 map1.get(key); double v2 map2.get(key); sum1 v1; sum2 v2; sum1Sq v1 * v1; sum2Sq v2 * v2; pSum v1 * v2; } int n commonKeys.size(); double numerator pSum - (sum1 * sum2 / n); double denominator Math.sqrt( (sum1Sq - sum1 * sum1 / n) * (sum2Sq - sum2 * sum2 / n) ); if (denominator 0) return 0.0; return numerator / denominator; } }这段代码是协同过滤的离线计算核心有四个关键参数要调。第一个是cron表达式每天凌晨3点跑避开业务高峰期。第二个是行为数据的窗口期我设定为30天窗口太短用户行为稀疏、相似度全为0窗口太长则带进大量过时兴趣。第三个是相似度阈值0.3低于这个值的用户对不写入数据库有效控制存储量。第四个是commonKeys.size() 2的过滤只有一两个共同阅读记录的相似度是纯运气直接归零更稳妥。3.3 内容推荐通道用HanLP提取关键词做新闻相似协同过滤只能覆盖有历史行为的用户新用户访问系统时行为表是空的必须靠内容推荐兜底。常见做法是用分词工具提取新闻关键词再算新闻间的相似度。以HanLP为例在pom.xml引入依赖后写一个提取关键词的服务dependency groupIdcom.hankcs/groupId artifactIdhanlp/artifactId versionportable-1.8.4/version /dependencyService public class ContentRecommendService { Autowired private NewsMapper newsMapper; // 从新闻正文提取关键词存入 news_keywords 表 public void extractKeywords(Long newsId) { News news newsMapper.selectById(newsId); String content news.getTitle() 。 news.getContent(); // 用HanLP提取前10个关键词 ListString keywords HanLP.extractKeyword(content, 10); // 简单权重方案位置靠前的关键词权重更高 StringBuilder sb new StringBuilder(); for (int i 0; i keywords.size(); i) { double weight 1.0 - (i * 0.05); // 第1个词权重1.0逐次递减 sb.append(keywords.get(i)).append(:).append(weight).append(,); } newsMapper.updateKeywords(newsId, sb.toString()); } // 根据用户最近读过的新闻推荐内容相似的新新闻 public ListNews recommendByContent(Long userId, int limit) { // 1. 取用户最近读过的5篇新闻的标签 ListLong readNewsIds behaviorMapper.selectRecentNewsIds(userId, 5); if (readNewsIds.isEmpty()) { return newsMapper.selectHotNews(limit); // 冷启动兜底 } // 2. 拼接所有标签词统计词频 MapString, Double tagWeightMap new HashMap(); for (Long newsId : readNewsIds) { String tags newsMapper.selectKeywords(newsId); for (String tagPart : tags.split(,)) { String[] kv tagPart.split(:); if (kv.length 2) { tagWeightMap.merge(kv[0], Double.parseDouble(kv[1]), Double::sum); } } } // 3. 查询包含这些标签且用户没读过的新闻 String joinedTags String.join(|, tagWeightMap.keySet()); return newsMapper.selectByTags(joinedTags, limit); } }这个实现的精妙之处在于关键词权重方案HanLP返回的关键词是按重要程度排序的第一个词通常是标题里的核心概念权重理应最高。我用1.0 - (i * 0.05)给位置靠前的词更高权重然后在聚合用户兴趣时用Double::sum把多篇新闻的标签权重叠加相当于给用户兴趣打了一个「加权标签云」。之后查询SELECT * FROM news WHERE tags REGEXP #{tags} AND id NOT IN (读过的) ORDER BY publish_time DESC LIMIT #{limit}就能拿到候选集。3.4 推荐主流程双通道合并与结果去重有了两个候选集需要一个门面Service把它们合并成最终推荐结果。推荐主流程的代码不多但逻辑顺序很关键Service public class RecommendFacadeService { Autowired private UserSimilarityService userSimilarityService; Autowired private ContentRecommendService contentRecommendService; Autowired private NewsMapper newsMapper; public ListNews recommend(Long userId, int limit) { // 1. 先走协同过滤通道 ListNews collabResults userSimilarityService.recommendBySimilarUsers(userId, limit / 2); // 2. 再走内容通道 ListNews contentResults contentRecommendService.recommendByContent(userId, limit); // 3. 合并去重 MapLong, News merged new LinkedHashMap(); for (News news : collabResults) { merged.put(news.getId(), news); } for (News news : contentResults) { merged.put(news.getId(), news); } // 4. 从结果中移除用户已读过的新闻 ListLong readIds behaviorMapper.selectRecentNewsIds(userId, 100); readIds.forEach(merged::remove); return new ArrayList(merged.values()).stream() .sorted(Comparator.comparing(News::getPublishTime).reversed()) .limit(limit) .collect(Collectors.toList()); } }合并逻辑里有两个细节容易被忽略。第一limit / 2的分流比例不是固定的协同过滤通道在用户行为充足时效果更好可以调到70%新用户或冷启动阶段内容通道占主导。第二LinkedHashMap保证插入顺序协同过滤的结果排在前面因为它的个性化程度更高。最后按publishTime倒序排序是因为新闻推荐必须优先展示新鲜内容不能拿一周前的旧闻充数。4. 推荐系统必踩的5个坑从行为日志丢失到推荐结果全是旧闻4.1 行为日志丢数据异步写入没做失败补偿现象用户阅读行为记录下来后发现统计数据比实际少很多推荐效果明显偏差。查数据库发现user_behavior表每天只有几百条记录但新闻详情页的访问量是几千。原因常见的错误写法是在Controller里直接调用behaviorMapper.insert()一旦数据库连接池满了或者网络抖动日志写入失败会直接抛出异常用户看到的页面就是500错误。要么为了不报错用try-catch吞掉异常日志彻底丢失。解决把行为写入改成异步 失败记录到本地表。SpringBoot里最简单的做法是用Async注解配合线程池Service public class BehaviorLogService { Async(behaviorExecutor) public void saveBehavior(Long userId, Long newsId, Integer type, Double value) { try { UserBehavior behavior new UserBehavior(); behavior.setUserId(userId); behavior.setNewsId(newsId); behavior.setBehaviorType(type); behavior.setBehaviorValue(value); behaviorMapper.insert(behavior); } catch (Exception e) { // 记一条补偿日志定时任务再补插 compensationMapper.insert(new BehaviorCompensation(userId, newsId, type, value)); } } }Async注解需要配置线程池避免默认的SimpleAsyncTaskExecutor每次新建线程导致资源耗尽。补偿日志表是后悔药定时任务每10分钟把补偿表里没写入成功的数据重试一次。这一套逻辑看着繁琐但在真实项目里它能扛住流量尖峰也是答辩时加分的设计点。4.2 皮尔逊相关系数分母为零冷启动用户让相似度计算直接翻车现象离线任务跑用户相似度时发现大量用户对之间的相似度是NaN数据库里插入了一堆空值。原因看pearson方法的代码denominator是Math.sqrt((sum1Sq - sum1*sum1/n) * (sum2Sq - sum2*sum2/n))。如果某个用户对同一篇新闻只点过一次所有行为值相同或者两个用户共同评分项只有一条这个括号里的值可能为负或零开根号后就是NaN或Infinity。解决我在代码里加了denominator 0的判断但实践中还要处理denominator 0的情况——浮点运算精度导致的结果。稳妥的做法是给分母取绝对值再加一个极小值兜底double denominator Math.sqrt(Math.abs((sum1Sq - sum1 * sum1 / n) * (sum2Sq - sum2 * sum2 / n)) 1e-9); if (Double.isNaN(numerator / denominator)) { return 0.0; }Math.abs强制非负加上1e-9防除零这样函数永远不会返回NaN。这种细节在算法课上不会讲但工程里必须处理否则定时任务每次跑完都会往表里写垃圾数据。4.3 推荐结果全是旧新闻时效性权重缺失现象推荐接口上线后用户反馈看到的新闻都是几天前的点进去没兴趣。协同过滤推荐的是「相似用户读过什么」但相似用户读的可能是昨天的内容。原因新闻是强时效内容而协同过滤只关注「兴趣相似」不考虑「时间新鲜」。用户昨天读了三篇科技新闻今天凌晨爬虫抓了10条新的科技新闻但协同过滤的结果里一条都没有。解决在排序阶段加入时间衰减因子。推荐结果排序时不光看相似度还要乘以Math.exp(-ageHours / 72.0)。72小时是半衰期超过3天的新闻热度指数级衰减double timeDecay Math.exp(-ageHours / 72.0); finalScore similarityScore * 0.7 timeDecay * 0.3;这个参数调整很玄学但有个可参照的经验新闻领域半衰期设48-72小时电商领域可以放宽到7天。排序接口里实践后你会发现光加这一条推荐效果的用户反馈就有明显变化。4.4 标签权重全被热门关键词霸占TF-IDF的IDF部分缺失现象内容推荐通道推荐出的新闻总是带有「中国」「今天」「记者」这些高频词用户兴趣被带偏。原因我只用了HanLP的extractKeyword它内部做了TF统计但IDF不够强导致全站每个新闻都有的通用词拿到高权重。这些词没有区分度不能代表用户的真实兴趣。解决在服务里加一个停用词表把「今天」「昨日」「报道」「记者」「中国」这类词过滤掉。停用词表不需要多复杂几十个词就能挡掉大部分噪声private static final SetString STOP_WORDS new HashSet(Arrays.asList( 今天, 昨日, 近日, 记者, 报道, 表示, 目前, 中国, 美国, 政府, 问题 )); ListString rawKeywords HanLP.extractKeyword(content, 20); ListString filteredKeywords rawKeywords.stream() .filter(word - !STOP_WORDS.contains(word)) .filter(word - word.length() 1) .limit(10) .collect(Collectors.toList());注意要先多提取几个词再过滤否则过滤后数量不够用。word.length() 1因为是新闻领域单字词基本都是虚词。这套组合处理完后标签的区分度会改善很多。4.5 论文里的准确率和实测对不上离线评估指标选错了现象源码包自带的论文里写着模型准确率95%自己跑出来的结果只有30%。答辩时老师一追问场面很尴尬。原因论文里的准确率大概率是「推荐列表里用户点击的比例」用了不同口径或者训练集和测试集划分方式不一样。新闻推荐里如果按随机划分把用户早期行为和后期行为混在一起相当于用已有兴趣预测已有兴趣准确率虚高。解决推荐系统评估要用时间序列划分而非随机划分。取用户前70%时间的行为做训练后30%做测试推荐结果命中测试集的比率才是有效指标。这个口径在论文里要写清楚答辩时也能站得住脚。5. 把推荐效果调到肉眼可见三个关键参数与一次完整调优记录5.1 相似度阈值0.3不是拍脑袋是稀疏度决定的离线相似度计算里我设了similarity 0.3才写入数据库。很多人会问这个0.3怎么来的其实它取决于你的数据稀疏度。如果每个用户平均只有10条行为记录用户间共同阅读的新闻极少皮尔逊系数天然偏低0.3的阈值会把大多数有效用户对过滤掉。我的调参方法是先跑一次不带阈值的计算统计相似度分布看中位数是多少然后取中位数以上作为阈值。日志里打印分布// 伪代码统计相似度分布 ListDouble allSims new ArrayList(); for (int i 0; i n; i) { for (int j i 1; j n; j) { allSims.add(pearson(...)); } } Collections.sort(allSims); double median allSims.get(allSims.size() / 2); double p75 allSims.get((int)(allSims.size() * 0.75)); log.info(相似度分布中位数{}, 75分位{}, median, p75);如果中位数只有0.25那阈值就应该下调到0.2如果中位数在0.450.3可以保留。这套「看分布定阈值」的方法比拍脑袋靠谱得多任何推荐系统的参数都应该这么做。5.2 行为权重拉大差距点击1分、点赞2分、收藏3分背后的逻辑权重方案直接影响协同过滤算出的相似度。默认点击1.0、点赞2.0、收藏3.0但这里的参数值得根据你的新闻类型微调。如果是娱乐八卦新闻用户看到标题就点点击行为含金量低点赞权重应该拉到3.0如果是深度技术教程能读完30秒以上的用户才是真感兴趣的duration 30的行为值得单独给权重。我常用的调整手段是在落库前根据behavior_type和duration重算权重public Double calcWeight(Integer behaviorType, Integer duration) { switch (behaviorType) { case 1: return duration ! null duration 30 ? 1.5 : 0.5; // 短点击降权 case 2: return 2.0; case 3: return 3.0; default: return 1.0; } }这个函数加到行为写入链路里即可生效。调整后你会发现单纯手滑点进去的新闻对用户画像的污染明显减轻。5.3 冷启动阶段的特殊处理新用户给热门榜新新闻给分类推荐冷启动是推荐系统里最经典的坑。新用户没有任何行为数据协同过滤直接跪内容推荐也因为读不到历史而失效。我的兜底方案分两段新用户先看到热门榜这个热门榜按行为热度计算而非单纯点击量这样推荐不至于太水新新闻靠分类匹配用户登录时选过一次兴趣分类或授权地理位置就可以按分类给内容推荐。前端传参时后端要做一次校验public ListNews recommendForNewUser(Long userId, int limit) { // 查用户是否已有行为记录 int behaviorCount behaviorMapper.countByUserId(userId); if (behaviorCount 5) { // 行为太少直接返回热门榜并混入用户注册时选择的分类新闻 ListNews hotNews newsMapper.selectHotNews(limit / 2); ListNews categoryNews newsMapper.selectByCategory(科技, limit / 2); return mergeAndDeduplicate(hotNews, categoryNews); } return recommend(userId, limit); }阈值设为5次行为很关键。少于5条行为记录时兴趣模型完全是噪声硬推反而体验差。5条之后就切换成正式推荐逻辑用户能感觉到推荐慢慢变准这种「从大众到个性」的过渡感在产品上是有设计价值的。5.4 一次真实调优从点击率6%到18%的完整参数调整顺序我在测试环境跑过一次完整的调优流程过程可以照抄。初始状态是默认参数点击率只有6%问题很多。第一步把行为权重从全部1.0改成点击0.5、点赞2.0、收藏3.0点击率升到9%第二步发现协同过滤输出的结果大多来自相似用户读过的旧新闻引入72小时时间衰减点击率升到13%第三步发现内容推荐的标签被停用词污染加了停用词表后标签更精准点击率升到16%第四步调相似度阈值分析了分布后从0.3降到0.2召回率提升最终点击率稳定在18%。调优的顺序也很讲究先修数据质量再调算法参数。如果行为权重还没区分度就去调时间衰减模型会把垃圾标签上的噪声放大。这套顺序在每次新项目里都很管用。回想我自己第一次做推荐系统踩了无数坑之后才明白一个道理推荐算法的效果不取决于你用了多高级的模型而取决于行为数据的质量和参数与场景的匹配度。哪怕是纯协同过滤加上简单的TF-IDF只要行为采集完整、权重设计合理、冷启动兜底到位效果就足够让人满意。反过来模型再花哨数据里有大量噪声最终结果也不会好。希望这些经验能帮你在做自己的新闻推荐系统时少走弯路。从建表到算法落地从调参到避坑照着这条路径逐步走你手上的SpringBoot项目会从「一个能跑的CRUD」变成「一个能讲出设计思路的完整系统」无论是交作业还是面试展示都能拿得出手。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
I2C信号排查实战:从万用表到逻辑分析仪的完整指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 6:26:20
dede蜘蛛爬行插件实操:主动引爬与爬行深度控制 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 6:26:20
Python Web安全渗透测试工具集成源码拆解:从脚本到工程化扫描器 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 6:26:20
ESXi将USB硬盘映射为本地磁盘并创建VMFS的完整实操指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 7:33:38
开源LLM代码审查工作流:CLI+Git原生集成实践 1. 项目概述:这不是一个“工具”,而是一套可落地的开源代码审查工作流open-code-review 这个名字乍看像某个具体软件,但实际它代表的是一类正在快速成型的新型开发实践——用开源、透明、可审计的方式,把大语言模型(LL… · 2026/9/25 7:33:38
用Trellis驯服AI编码代理:规范文件如何让代码不再失控 1. AI编码代理的失控时刻:为什么没人敢放手让它写代码如果你这段时间用过Cursor、Windsurf这类AI编程工具,八成已经体会过那种"又爽又怕"的感觉。爽的是,一个前端页面、一个后台接口、一段脚本,敲几行提示词就出来了&am… · 2026/9/25 7:33:38
HC32L13x Keil编译报错__WEAK undefined:根因排查与中断函数正确写法 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 7:33:32
VSCode+MinGW+CMake嵌入式C开发环境搭建指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 7:33:32
优化等级从-Og改-O2就崩溃?嵌入式C代码的volatile与未定义行为排查指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 7:33:32
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37