
短剧这两年火到什么程度呢我身边不少朋友晚上刷短剧能刷到凌晨两三点但平台推荐不准的时候用户流失也特别快。正因如此短剧推荐系统成了很多开发者钻研的方向尤其是用SSMSpring SpringMVC MyBatis这套经典Java框架来做推荐后端既能练手Spring体系又能把推荐算法真正落到Web项目里。这篇文章就把我做“基于SSM的短剧推荐系统”从设计到实现的过程完整拆开讲一遍包括数据库设计、用户行为采集、协同过滤实现、推荐接口开发以及我踩过的各种坑。适合正在做相关毕业设计、课程设计或者想入门推荐系统开发的Java后端同学参考。1. 项目整体设计与思路拆解1.1 为什么选SSM这套技术栈先聊技术选型。最近几年Spring Boot越来越普及新人写项目基本都用自动配置但SSM依然是很多高校和企业老项目的标配。SSM就是Spring SpringMVC MyBatis三个框架各管一摊Spring管对象生命周期和事务SpringMVC管HTTP请求分发MyBatis管数据库访问。选它做短剧推荐系统有几个很现实的原因。第一相比Spring BootSSM要求手动配置会逼着你把Servlet容器、IoC容器、MyBatis会话工厂这些东西的原理弄清楚对理解Java Web底层特别有帮助。第二很多课程设计和毕业设计题目就指定用这个技术栈资料多、容易找到参考。第三以后如果去维护老项目SSM经验是实打实的硬通货。有人会问推荐系统用Python不是更合适吗从算法模型角度讲Python生态确实更强。但短剧推荐系统的核心不是训练一个多复杂的模型而是要把推荐逻辑嵌入到整个Web业务里包括用户登录、行为记录、推荐结果展示、后台管理。用Java/SSM做工程化落地好处是跟业务系统无缝集成事务、权限、部署都方便性能也足够。算法部分用经典的协同过滤和基于内容的推荐完全可以在Java里实现不需要额外搭一套Python服务。这样项目结构简单部署也轻。1.2 系统功能模块怎么划分一开始别急着写代码先把功能边界画清楚。我把它分成前台用户端和后台管理端两个大模块。前台用户端主要负责C端体验注册登录用户名加密码基于Session或Token做登录态。短剧浏览分类浏览、关键词搜索、分页列表、短剧详情。用户行为记录观看短剧、点赞、收藏、评分、搜索关键词。个性化推荐首页推荐、相似推荐、热门榜单。后台管理端则给运营和系统维护者使用短剧管理上架下架、修改封面、简介、剧集集数。分类管理都市、古装、甜宠、悬疑、逆袭等。用户管理禁用用户、查看用户行为日志。推荐管理查看推荐结果、推荐参数配置、人工置顶。为什么推荐系统里要专门留一个管理端因为线上推荐不能完全交給算法。运营同学经常会手动干预比如某部新剧要重点推或者节假日要把某类短剧排在前面。所以在设计短剧表和推荐日志表时要预留人工权重字段算法排序时会把运营权重一并考虑进去。这个细节在很多纯算法教程里不会讲但实际项目里特别关键。1.3 推荐方案选型先想清楚核心矛盾短剧推荐和电商推荐最大的不同在于短剧内容短、数量多、用户观看决策成本低。用户可能一天刷几十个短视频行为数据会比较稠密但“兴趣漂移”也很快——上周喜欢甜宠这周就可能迷上悬疑。所以不能用一套静态算法打天下我最终采用的是三层融合方案。基于用户的协同过滤UserCF找到和你口味相似的用户推荐他们看过而你没看过的短剧。基于内容的推荐Content-based分析短剧的标签、分类、简介关键词推荐与你历史喜欢内容相似的剧。热度与新品补偿Hot/New解决冷启动新用户和老用户都会看到一部分热门或最新上架的短剧避免推荐池太偏。这三者通过加权融合排序初始权重我设的是4:4:2。这个比例不是拍脑袋定的而是根据小流量测试观察点击率调出来的。为什么不能只做协同过滤因为短剧上新太快新剧几乎没有用户行为数据协同过滤对它们完全无效。为什么不能只做内容推荐因为内容推荐天然容易“信息茧房”推荐来推荐去都是同一类题材用户很快会审美疲劳。两者结合再叠加热门兜底才能同时照顾个性化和多样性。2. 核心细节解析与实操要点2.1 数据库表设计先想清楚存储什么推荐系统的数据模型比普通CRUD复杂一点因为要沉淀用户行为。我把核心表分成三类基础信息表、行为表和推荐结果表。基础信息表包括用户表user、短剧表short_drama、分类表category。用户表核心字段有username、password、nickname、avatar、created_at、status。短剧表要比普通内容表多一点运营字段title、cover_url、description、category_id、tags、director、actor、total_episodes、total_likes、total_plays、status、is_recommend、weight、created_at。tags字段很重要后面做基于内容推荐时直接依赖它。行为表包括观看历史watch_history、评分表rating、收藏表favorite、搜索日志search_log。观看历史要记录id、user_id、drama_id、episode、duration、total_duration、watch_time、finish_flag。这里duration是用户实际观看的有效时长total_duration是整集总时长finish_flag表示是否看到结尾。很多项目忽略完播率只记“看了没看”这是很大的浪费。短剧推荐里完播率是比播放次数更可靠的兴趣信号。推荐结果表recommendation_log重点记录id、user_id、drama_id、reason_code、position、create_time。推荐结果落库不是必须的但对运营和答辩非常有帮助。通过reason_code可以回答“用户为什么看到这部剧”比如USER_CF代表协同过滤CONTENT代表内容相似HOT代表热门ADMIN代表人工置顶。设计表时还有几个容易踩的点行为表要加联合唯一索引比如rating表加(user_id, drama_id)favorite表同理防止重复写入。watch_history表按(user_id, watch_time)建联合索引查询用户近期行为会很快。short_drama表的category_id和total_plays加普通索引列表和排序会用到。不要把所有行为揉在一张表里后续数据清洗、统计都会被这张表拖死。2.2 用户行为采集推荐系统最容易被忽视的地基很多第一次做推荐系统的人喜欢一上来就写相似度算法结果跑起来发现效果很虚。为什么行为数据太脏或者太稀疏。我总结了一套行为采集和权重设计方法。要采集的行为至少有五类。第一点击播放用户进入详情页或点击播放按钮记一次播放。第二观看进度定时上报或离开页面时上报记录看了哪一集、看了多长时间。第三显式反馈用户主动评分、点赞、收藏权重最高。第四搜索行为用户搜索关键词反映强意图搜索结果点击也要记录。第五完播状态正常情况下短剧每集只有几分钟用户能看完说明内容很对胃口。接下来要把行为量化为评分供协同过滤使用。这里不需要用户真的给每部剧打分直接通过行为隐式换算。我自己用的公式是effective_score rating_score * 0.5 favorite * 2 play_finish_score search_score like_score具体映射规则如下评分存在时取实际评分否则基础分3分。收藏一次加2分点赞一次加1分。完播率大于0.7加1分0.3到0.7之间加0.5分。搜索后点击播放加0.8分。这个权重不是固定的。如果你的平台上用户特别喜欢点收藏那收藏权重可以再高一点如果大家只爱看不爱点就要靠完播率拉高区分度。数据采集是整个推荐系统里最不起眼、但决定上限的部分。算法再强喂进去的都是一堆垃圾特征结果也一定是一堆垃圾推荐。2.3 推荐算法如何在SSM项目里落地SSM项目虽然是Web框架但算法完全可以用Java写。以UserCF为例完整流程分四步。第一步构建用户-物品评分矩阵。从rating、favorite、watch_history等表里查出所有行为按userId分组得到一个MapLong, MapLong, Double。这里要注意行为数据量大的时候一次性全部load到内存可能撑不住所以通常只取最近30天的行为或者过滤掉只有一两次行为的“僵尸用户”。第二步计算用户相似度。常见方法有余弦相似度和皮尔逊相关系数。为了简单和稳定我用余弦相似度cos_sim(u, v) sum(r_ui * r_vi) / (sqrt(sum(r_ui^2)) * sqrt(sum(r_vi^2)))其中r_ui表示用户u对短剧i的评分。实现时只需要遍历两个用户评过分且都包含的短剧集合不要全表循环。第三步选取相似度最高的K个用户作为近邻。K通常取20到50数据量少的时候取10也可以。然后对这些近邻看过的、目标用户没看过的短剧做加权评分预测score(u, i) sum(sim(u, v) * r_vi) / sum(|sim(u, v)|)第四步过滤掉用户已经看过的短剧按预测分排序取TopN返回。这里过滤动作必须做否则推荐列表里会出现用户刚看完的那部剧体验非常差。内容推荐部分我会在service层加一个方法根据用户的高分短剧标签统计出偏好标签再从候选池里找包含这些标签的短剧按重合度排序。比如用户看过标签为“甜宠、逆袭”的剧那就优先推荐同样有这两个标签的剧。冷启动处理也要提前规划。新用户没有任何行为直接返回热门榜和最新上架同时可以在前端提示“大家都在看”而不是“为你推荐”避免显得不智能。新短剧没有行为数据可以通过内容推荐先推给标签匹配的用户同时给一小部分随机曝光让它有机会积累点击和完播数据。2.4 SSM分层架构与关键配置推荐系统和普通SSM项目的分层一致我习惯拆成这些包controller接收HTTP参数校验参数调用service返回统一Result对象。service业务逻辑包括用户管理、短剧管理、行为记录、推荐算法。dao/mapperMyBatis操作数据库只做SQL和对象映射。entity/model数据库实体类。common统一返回对象、全局异常处理、工具类。推荐算法放在service层的recommend包下比如UserCFServiceImpl、ContentRecommendServiceImpl、RecommendFacadeService。Facade模式对外提供一个统一入口controller只管调RecommendFacadeService不需要知道具体用的是协同过滤还是内容推荐。后续替换算法或者调整权重不会影响接口层。SSM整合的关键配置要从web.xml说起。web.xml里要配置SpringMVC的DispatcherServlet和ContextLoaderListener。SpringMVC的配置文件扫描controllerSpring的配置文件扫描service和mapper。两个容器要严格区分如果扫描范围交叉轻则bean加载两次重则事务失效。MyBatis的SqlSessionFactory在spring-mybatis.xml里配置数据源我用Druid连接池自带监控方便排查慢SQL。事务注解用Transactional注意它只对public方法生效同类内部调用不会走代理这点后面坑里细说。3. 实操过程与核心环节实现3.1 环境搭建与版本选型先列出我实测稳定的环境组合JDK 1.8Maven 3.6.3Tomcat 8.5MySQL 5.7Spring 5.2.22.RELEASESpringMVC 5.2.22.RELEASEMyBatis 3.5.9Druid 1.2.8Redis 5.0以上做结果缓存可选SSM最怕版本冲突。不建议全用最新版最新版有时会引入额外依赖约束或改动API。上面这个组合我用了很多次兼容性很稳至少不会因为框架版本不匹配导致不明不白的异常。搭建核心配置时要重点关注web.xml。代码如下context-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/spring-mybatis.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mapping注意spring-mybatis.xml和spring-mvc.xml必须分开。很多人图省事把数据源配置和controller扫描放在同一个文件里短期能跑但后面加AOP、事务、多数据源时很容易出问题。Spring根容器负责service、dao、数据源这些基础组件SpringMVC子容器只负责controller这是SSM整合的基本原则。3.2 用户协同过滤完整实现这里分享一个简化版的UserCF核心代码。为了控制篇幅只保留关键逻辑完整代码里还需要做参数校验、缓存、分页等。public ListDramaVO recommendForUser(Long userId, int topN) { // 1. 获取所有用户行为评分 MapLong, MapLong, Double userItemMap userBehaviorService.getUserItemScoreMap(); // 2. 计算当前用户与其他用户的相似度 MapLong, Double simMap new HashMap(); MapLong, Double targetItems userItemMap.get(userId); if (targetItems null || targetItems.isEmpty()) { return hotDramaService.getHotList(topN); } for (Map.EntryLong, MapLong, Double entry : userItemMap.entrySet()) { Long otherUserId entry.getKey(); if (otherUserId.equals(userId)) continue; double sim cosineSimilarity(targetItems, entry.getValue()); if (sim 0.3) { simMap.put(otherUserId, sim); } } // 3. 加权评分生成候选集 MapLong, Double candScore new HashMap(); for (Map.EntryLong, Double simEntry : simMap.entrySet()) { Long otherUserId simEntry.getKey(); double sim simEntry.getValue(); MapLong, Double otherItems userItemMap.get(otherUserId); for (Map.EntryLong, Double itemEntry : otherItems.entrySet()) { Long dramaId itemEntry.getKey(); if (targetItems.containsKey(dramaId)) continue; candScore.merge(dramaId, sim * itemEntry.getValue(), Double::sum); } } // 4. 排序取TopN再拼装短剧详情 ListMap.EntryLong, Double list new ArrayList(candScore.entrySet()); list.sort((a, b) - Double.compare(b.getValue(), a.getValue())); return buildTopNResult(userId, list, topN); }这段代码看起来不难实际有四个注意点。第一相似度阈值0.3是我调出来的。数据稀疏时可以降到0.2否则候选集太小推荐结果会很局限数据稠密时阈值可以提高到0.4减少噪声。第二targetItems这个map在第三步循环里会频繁查询所以不要用List去contains一定要用Map结构。第三Map.merge这个方法很多新手不熟它能在key不存在时直接放值存在时执行后面的累加函数比手动写if简洁多了。第四推荐结果一定要过滤已看短剧包括用户评分过的、收藏过的、观看过的都要排除。为了让推荐接口稳定我还在service层加了一层Redis缓存。每次计算完成后把userId对应的结果List存到Rediskey是rec:user:{userId}过期时间3小时。用户再进来时先读缓存没有缓存才重新计算。高并发场景下这个缓存能挡住绝大多数重复计算数据库和内存压力都会小很多。3.3 推荐接口与前端展示推荐接口我设计成三个GET /recommend/home 首页推荐返回多个分栏为你推荐、热门短剧、最新上架。GET /recommend/detail/{dramaId} 短剧详情页相似推荐基于短剧tags查询。GET /recommend/log 管理端查看推荐日志用于调试。返回格式统一用Result对象结构如下public class ResultT { private Integer code; private String message; private T data; // getter/setter省略 }前端展示时我会在每张短剧卡片下带一行推荐理由。这一步很便宜但效果很好。推荐理由来源是推荐日志里的reasonCodeUSER_CF因为你看过某类短剧和你口味相似的人都在看。CONTENT因为你喜欢“甜宠”“逆袭”这类标签。HOT大家都在看。ADMIN运营精选推荐。有了推荐理由用户会觉得系统“懂我”而不是一堆剧随机排列。当你看到“因为你喜欢甜宠”这句话时点进去的概率会明显提升。做课程设计或者答辩时这也是一个比其他项目更完整的亮点。3.4 管理端推荐参数配置管理端我做了几个可配置参数方便运营调整协同过滤近邻数K默认30。相似度阈值默认0.3。内容推荐权重、协同过滤权重、热度权重默认0.4、0.4、0.2。人工置顶短剧ID和排序权重。这些配置存在sys_config表里系统启动时加载到内存。后台修改后通过配置刷新接口重新加载不需要重启服务。这样运营发现某段时间热门剧权重太低首页推得过于冷门可以直接调高热度权重很快见效。这个管理功能的开发工作量不大但价值很高。推荐系统不是上线后就不管了而是需要根据反馈不断调参。提前把这些参数做成可配置后续迭代会方便很多。4. 常见问题与排查技巧实录4.1 SSM启动和运行期的高频报错做这个项目时我踩了不少坑挑几个有代表性的说一说。第一个是Spring容器启动就报BeanCreationException。常见原因有三个service实现类没有被扫描到、接口和实现类没有对应、依赖的bean在配置文件里没定义。排查技巧是看控制台第一个异常不要盯着最底部看。如果提示No qualifying bean of type先把包扫描范围核对一遍再用Spring测试单元跑一下context加载能快速缩小问题范围。第二个是Mapper接口绑定异常。MyBatis里Mapper接口和XML的namespace必须完全一致statementId要和接口方法名一致否则会报BindingException。我习惯把XML文件放在resources/mapper目录并且保持与接口同包名、同名文件这样Maven打包时会自动带上不容易漏。还要检查mybatis配置里mapper-locations是否写成classpath:mapper/*.xml。第三个是中文乱码。前端传到后端乱码多半是Tomcat的URIEncoding没设UTF-8数据库存乱码要看MySQL连接串是否加了characterEncodingutf8以及表结构里的字段collation。连接串推荐写成下面这样jdbc:mysql://localhost:3306/short_drama?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai第四个是事务不生效。这个很隐蔽。我在给用户行为批量写入时加了Transactional但测试时发现异常后数据还是部分写进去了。排查后发现我在同一个类里调用了另一个加了Transactional的方法而Spring AOP默认不会代理this调用所以事务没有生效。解决办法有两个把两个方法拆到不同的Spring bean里或者通过AopContext.currentProxy()获取代理对象再调用。另外事务方法必须public也不能在finally里把异常吞掉。4.2 推荐效果不理想先别急着换算法系统上线后最常被问的是“推荐结果不准怎么办”。我踩过坑之后总结了一套排查顺序。第一确认行为数据有没有积累够。刚上线时每个用户可能只有三五个行为协同过滤基本失效推荐效果差是正常的。这时候先用热门榜兜底等数据量上来再逐步加大算法权重。第二看相似度阈值。阈值太高候选集很小用户看到的总是一模一样的剧阈值太低噪声变大推荐一些完全不相关的内容。我的经验是先设0.3然后统计候选集平均大小。如果平均候选不到20就降到0.25甚至0.2。第三看评分口径。如果隐式评分里收藏只有2分但完播率只有1分而用户又偏偏不喜欢点收藏那推荐结果会偏向“看过的剧”而不是“喜欢的剧”。可以调高收藏权重或者对完播率做非线性加成比如完播率超过0.9直接加2分。第四冷门的剧被过度推荐。原因是热门短剧总播放量大相似度计算时容易占优势。我后来在计算热度分时加入了对数平滑score_hot log10(total_plays 1) * 0.1 total_likes * 0.15这样大热剧不会完全霸榜长尾剧也有机会露脸。第五线上评测不能只看准确率和召回率。业务上更关心用户点击率和观看时长。我做过最简单的A/B测试把用户流量分成两组A组用旧算法B组用新算法观察三天内的点击率变化。这个结果比跑一堆离线指标更有说服力。4.3 从SSM迁移到Spring Boot的一点观察写这套SSM项目的过程本质上是把Spring Boot里那些自动配置的东西手动做了一遍。后面如果想把项目改造成Spring Boot可以很清楚地找到对应关系web.xml对应DispatcherServletAutoConfigurationspring-mybatis.xml对应DataSourceAutoConfiguration和MyBatisAutoConfigurationEnableTransactionManagement对应事务自动配置。知道了底层逻辑用Spring Boot做短剧推荐系统会更快。但新手如果直接上手Spring Boot容易遇到“依赖报错但不知道去哪儿改配置”的困境。所以我还是建议先啃一遍SSM搞清楚“配置文件为什么这么写”再去做自动配置项目会通透很多。这也是我坚持用SSM作为课程设计和毕业设计技术栈的原因。最后分享一点个人体会写到这里这个基于SSM的短剧推荐系统的主体架构和实现细节基本说完了。我个人实操下来最大的体会是推荐系统真正难的不是算法而是把“用户偏好”和“内容特征”用工程手段表达出来。算法模型再花哨如果行为数据没埋好、数据库设计不清晰、推荐过程不可解释用户照样不买账。哪怕只用经典的协同过滤加内容推荐只要把数据采集做扎实推荐效果已经能超过很多只套模板的项目。最后再分享一个小技巧如果你也准备做这个题目建议先花两天时间设计好行为日志表和推荐理由代码再写相似度算法。日志表设计好了后面调优才有依据推荐理由字段虽然只是展示用但会让整个系统很有“人情味”答辩时也能讲出亮点。这个项目后续可以扩展的方向很多比如基于隐语义模型的推荐、基于Redis的实时推荐、用Spark做离线批量计算但每一步都建议在现有工程上小步迭代别一上来就上全家桶。上面的代码和思路大家照着实现一遍吃到自己的项目里比反复看教程有用得多。