资讯动态

基于SSM框架的服装搭配推荐系统实战:从数据库到协同过滤全流程解析

发布时间:2026/10/7 3:00:45 来源:尧图企业网站定制
最近帮实验室的学弟梳理毕设代码正好整理到一套服装搭配推荐系统整套是用 Java SSM 框架Spring SpringMVC MyBatis做的。这类计算机毕设题目在本科里非常常见但真正能把推荐这件事做明白、把前后端全流程跑通的人其实不多。这篇文章我就把这套系统的完整开发链路写出来包括选题思路、数据库表设计、推荐算法落地、核心接口代码以及我在开发过程中踩过的坑和答辩被问到的典型问题。这套系统按功能划分大概是这样的用户端负责注册登录、浏览服装和搭配、收藏搭配、给搭配打分、维护个人衣橱推荐端根据用户的历史行为做协同过滤推荐同时结合服装标签规则自动生成搭配管理端负责服装管理、搭配管理、标签管理、用户管理和基础数据统计。整体技术栈完全围绕 SSM 展开前端使用 JSP Bootstrap数据库用 MySQL服务器用 Tomcat。如果你正在纠结毕设选题或者已经选了类似题目但不知道从哪下手这篇内容应该能帮你省不少时间。1. 毕设选题逻辑服装搭配推荐系统解决的是什么问题很多人做毕设选题时容易走两个极端要么选一个烂大街的管理系统要么选一个明显超出本科能力范围的基于深度学习的某某系统。说实话这两种都不是好选择。管理系统技术含量低答辩时评委问两句就露馅深度学习选题如果没有数据、没有算力最后基本是在调包。服装搭配推荐系统恰好处于一个比较舒服的位置——它有一个清晰的核心算法点又有足够多的业务功能来展示工程能力。1.1 为什么这个题目适合用 Java SSM 实现推荐系统听起来高大上但落到服装搭配场景它不需要分布式、不需要海量数据一个单机应用完全能承载。这就给了你用经典 Java 技术栈实现的空间。SSM 框架在这个题目里承担的角色非常明确Spring 负责 Bean 管理和事务控制。SpringMVC 负责前端请求的映射和转发。MyBatis 负责数据库的持久化操作尤其适合你后面要写的复杂多表联合查询。另外大多数高校的 Java 课程和实训环节都是围绕 SSM 或 Spring Boot 展开的用 SSM 做不会显得太超纲答辩时也能解释清楚每一层的作用。如果你想在 SSM 基础上加一点差异化可以在推荐算法上做文章而不是换框架。1.2 系统的目标用户和核心场景这套系统不是给电商巨头设计的它的使用场景更像一个个人穿搭助手。目标用户大致有三类第一类是早上出门不知道穿什么的人需要系统根据当天场景推荐搭配第二类是买了新衣服不知道怎么配的人需要看到完整搭配方案第三类是纯粹喜欢浏览穿搭内容的人需要系统帮忙筛选出风格相近的搭配。围绕这些场景我设计了几个核心用例用户注册登录完善自己的风格偏好和尺码、季节偏好。浏览服装库按分类、季节、风格筛选。查看系统生成的搭配结果收藏或评分。系统记录用户行为基于行为数据生成个性化推荐。管理员在后台维护服装和搭配数据。这个用例列表就是你系统功能的骨架。后面设计数据库表和页面时每一步都要能对应到这个用例列表上。2. SSM 框架的架构分层与项目初始化很多同学拿着 SSM 项目模板就开始写代码但项目结构为什么要分成 controller、service、mapper 三层说不清楚。这里我把每层在这个系统里的具体任务讲明白。2.1 为什么毕设还在用 SSM 而不是 Spring Boot这个问题几乎每次答辩都会被问到你得先有答案。Spring Boot 本质上是对 Spring 生态的自动装配封装开发效率确实比 SSM 高但它把很多配置细节藏起来了。对于毕设来说SSM 的 XML 配置虽然繁琐却逼着你把 Spring IOC、AOP、事务传播机制这些核心概念全部搞懂。评审老师也更愿意看到你能手写配置、能解释 Bean 生命周期。如果你最终决定用 Spring Boot 做也请务必把自动配置的原理弄清楚否则答辩风险很大。我见过太多用 Spring Boot 的同学被问spring-boot-starter-web 里到底做了什么时答不上来。2.2 项目骨架搭建三层架构与包结构设计我建的项目包结构如下每个包职责单一后期好维护com.campus.dress ├── controller // 接口层接收请求返回结果 ├── service // 业务层核心逻辑事务控制 │ └── impl ├── mapper // 数据层MyBatis 的 Mapper 接口 ├── entity // 实体类对应数据库表 ├── dto // 数据传输对象接口入参/出参 ├── common // 公共工具分页、返回结果封装 └── config // 配置类spring-mvc.xml、mybatis-config.xml这里面比较容易忽略的是 dto 层。有些同学直接把 entity 返回到前端把数据库表结构暴露给页面很不好。比如搭配推荐的结果需要包含服装列表、搭配封面、总评分等多个来源的数据必须通过一个 RecommendationVO 来封装。我的习惯是entity 只描述单张表凡是需要跨表组合的数据都定义 DTO并由 service 层负责组装。2.3 Spring、SpringMVC、MyBatis 三个配置文件的职责边界用 SSM 必然要维护至少四个配置文件web.xml配置 DispatcherServlet、ContextLoaderListener 和编码过滤器。springmvc-servlet.xml只扫 controller 包配置视图解析器和静态资源映射。applicationContext.xml扫 service 包配数据源、事务管理器、开启注解事务。mybatis-config.xml配置别名、下划线转驼峰、Mapper 扫描路径。这里最重要的一个实践是扫描器的使用。springmvc 的扫描器只扫 controllerspring 的扫描器只扫 service 和 mapper两者不要扫重。否则事务代理会被 MVC 容器加载导致 Transactional 失效这个问题我在后面踩坑章节会详细讲。3. 重点来了数据库表结构怎么设计推荐系统能不能跑起来数据库设计占了七成功夫。我第一版很快写完了结果实测验时才发现最初的表结构把搭配存成了一个字符串字段根本无法支撑推荐算法。后来老老实实把表和关系梳理成了下面这套。3.1 核心表清单这套系统涉及的表大概九张按模块划分如下模块表名主要字段说明用户userid, username, password, nickname, avatar, gender, preferred_style记录基础信息和风格偏好服装clothingid, name, image, category, color, style, season, price, descriptioncategory 分为上装/下装/外套/鞋靴/配饰标签tagid, tag_name, tag_type服装标签如通勤街头甜美关联clothing_tagclothing_id, tag_id服装与标签多对多搭配outfitid, name, cover_image, style, season, description一条搭配方案搭配明细outfit_itemid, outfit_id, clothing_id, slotslot 表示该服装在搭配中的角色1 上装2 下装3 外套4 鞋靴收藏user_favoriteid, user_id, outfit_id收藏行为评分user_ratingid, user_id, outfit_id, score, create_time1-5 分评分浏览记录user_behaviorid, user_id, outfit_id, behavior_type, create_timetype 分为 view / favorite / rate这里面最关键的设计思路是搭配不是一张表里的一个字段而是一张独立的实体表加一张明细表。每个搭配可以包含多件服装每件服装有一个槽位。这样一来推荐算法就能根据搭配明细去计算这件上装是否经常和这类下装一起出现从而挖掘出搭配规则。3.2 服装标签与搭配关系的建模服装搭配的本质其实是标签体系的匹配。我给每件服装打了至少两个维度的标签风格标签职场通勤、休闲街头、甜美可爱、运动户外和适用场景标签上班、约会、运动、旅行。标签存成多对多的关系用 clothing_tag 关联表来维护。举例说明一件白色衬衫的风格标签是通勤场景标签是上班。它在 outfit_item 里跟一条黑色直筒西裤出现在同一个 outfit 里系统就可以统计出一个规则衬衫类服装 西裤类服装在当前搭配数据中出现次数最多于是后续生成新搭配时优先组合这两个类别。这套思路在 SQL 上就是一个 group by 和 count 的统计实现成本很低但效果直观答辩时也容易讲清楚。3.3 用户行为表的设计推荐系统的核心是用户行为数据。我在最初设计时只想着实体数据差点漏掉用户行为记录。实际上推荐算法依赖的正是三张行为来源user_favorite收藏了哪些搭配表示强偏好。user_rating打了多少分表示量化偏好评分权重最高。user_behavior浏览了哪些搭配表示弱兴趣。这三种行为在计算时权重不同我在 service 层设计了一个打分规则一次浏览记 1 分一次收藏记 3 分一次 5 星评分记 5 分。这样就能把三类行为统一折算成用户对搭配的隐式评分输入到协同过滤算法里。表结构不需要把权重存进数据库权重属于业务规则写在代码里即可。4. 推荐系统的实现协同过滤 搭配规则推荐算法是整个项目的灵魂也是拿高分的关键。我最终实现的是两套策略的组合一套是基于用户的协同过滤UserCF另一套是基于标签聚合的搭配规则生成。前者解决猜你喜欢问题后者解决自动生成新搭配问题。4.1 基于用户的协同过滤算法思路UserCF 的核心假设是跟你兴趣相似的人喜欢的搭配你也大概率喜欢。整个计算过程分四步第一步构建用户-搭配评分矩阵。从 user_rating、user_favorite、user_behavior 三张表里把所有行为取出来在 Service 层折算成用户对搭配的评分。没有行为记录的算 0 分矩阵用 Map 存外层 key 是 userId内层 key 是 outfitIdvalue 是折算后的评分。第二步计算目标用户和其他用户的相似度。我用的是余弦相似度公式。假设两个用户对一组搭配的评分为向量 A 和 B相似度等于两个向量的点积除以模长的乘积。第三步选取 K 个最近邻用户。K 我取 10太近推荐结果会局限在小圈子里太远又会引入不相关用户。第四步预测目标用户对候选搭配的评分按预测分取 Top-N。预测评分公式如下预测评分 目标用户平均分 最近邻的加权差值的平均简单解释一下每个近邻用户对同一搭配的评分减去该近邻自己的平均评分得到一个差值再按照相似度加权汇总。加上目标用户的平均分就得到目标用户对这个搭配的预测评分然后排序取前 N 个作为推荐结果。这段逻辑我拆到了三个 Service 方法里保证每个方法只做一件事代码量不大但思路清晰。4.2 服装搭配规则引擎基于共现统计的自动搭配如果系统只有协同过滤冷启动阶段你没有行为数据推荐结果就是空的。所以我又做了一个规则引擎用来无中生有地生成搭配方案。规则引擎的原理来自关联规则思想如果大量已存在的搭配里某两个细分类别的服装频繁同时出现那这两个细分类别就更可能构成合理搭配。具体实现分两步。第一步统计共现频率。用一条 SQL 查询 outfit_item 表统计同一 outfit 里不同 slot 组合的服装类别对出现次数。SELECT a.clothing_category AS top_category, b.clothing_category AS bottom_category, COUNT(*) AS cnt FROM outfit_item a JOIN outfit_item b ON a.outfit_id b.outfit_id WHERE a.slot 1 AND b.slot 2 GROUP BY a.clothing_category, b.clothing_category ORDER BY cnt DESC第二步按规则生成候选搭配。比如统计结果里衬衫 西裤的组合出现了 18 次衬衫 牛仔裤出现了 5 次那么生成新搭配时我会优先从服装库里挑一件风格标签匹配通勤的衬衫和西裤组合。组合还不够还要加一道风格校验两件服装共有的标签越多基础匹配分越高季节和场景标签一致再加分。最终按总分排序取最高分作为推荐搭配。这套规则引擎的运行过程其实是先统计后生成比协同过滤多了一个主动生成的动作。答辩时你可以把这两种策略对比着讲UserCF 擅长个性化召回规则引擎擅长冷启动和多样性。4.3 冷启动阶段的兜底策略冷启动分为用户冷启动和物品冷启动。用户冷启动新用户没有任何行为数据协同过滤完全没有输入。我的处理方式是注册时让用户选择风格偏好通勤、休闲、甜美、运动系统先按该风格标签从服装库里挑出搭配按浏览量和收藏量排序作为新人推荐。物品冷启动新上架的服装或搭配没有用户行为数据。我的思路是先用规则引擎给它匹配同风格、同季节的服装生成预设搭配如果实在没有可匹配的就按发布时间倒序推给用户保证每件新服装至少有曝光机会。5. 核心功能实现与关键代码说完了算法接下来看代码怎么落地。我挑了几个最有代表性的实现片段包括推荐接口的调用链、MyBatis 动态 SQL、以及图片上传这种每个系统都会碰到的功能。5.1 推荐接口的实现流程前端发起一个 GET /recommend 请求携带参数 userId 和 pageNum。Controller 接收到请求后先调用 RecommendService由 Service 内部判断用户是否有足够的行为数据。如果有走 UserCF如果没有走规则引擎或热门推荐。核心 Service 实现伪代码如下Service public class RecommendServiceImpl implements RecommendService { Autowired private UserBehaviorMapper behaviorMapper; Autowired private OutfitMapper outfitMapper; Autowired private RuleEngine ruleEngine; Override public ListRecommendationVO recommend(Integer userId, int topN) { // 1. 读取行为数据并构建评分矩阵 ListUserBehavior behaviors behaviorMapper.selectByUserId(userId); if (behaviors null || behaviors.isEmpty()) { // 冷启动走规则引擎 return ruleEngine.generateByUserPreference(userId, topN); } // 2. 构建用户-搭配评分矩阵 MapInteger, MapInteger, Double userItemMatrix buildMatrix(); // 3. 计算相似用户 MapInteger, Double similarityMap calcSimilarity(userId, userItemMatrix); // 4. 取 K 近邻 ListInteger neighbors selectNeighbors(similarityMap, K); // 5. 预测评分 ListRecommendationVO result predictScore(userId, neighbors, userItemMatrix, similarityMap); // 6. 过滤已经收藏或评分的搭配 result.removeIf(vo - behaviorMapper.existsBehavior(userId, vo.getOutfitId())); return result.stream().limit(topN).collect(Collectors.toList()); } }这里面每个子方法我都单独实现。calcSimilarity 里用余弦相似度 java private double cosineSimilarity(MapInteger, Double userA, MapInteger, Double userB) { SetInteger common new HashSet(userA.keySet()); common.retainAll(userB.keySet()); if (common.isEmpty()) { return 0.0; } double dot 0, normA 0, normB 0; for (Integer outfitId : common) { dot userA.get(outfitId) * userB.get(outfitId); } for (double v : userA.values()) { normA v * v; } for (double v : userB.values()) { normB v * v; } if (normA 0 || normB 0) { return 0.0; } return dot / (Math.sqrt(normA) * Math.sqrt(normB)); }注意一点协同过滤在数据量很小比如只有几十个人注册时效果其实不明显这是数据稀疏性问题。我在 Demo 测试时如果发现推荐列表和热门列表基本重合会适当降低冷启动判定阈值让新的行为数据更快进入推荐。5.2 MyBatis 动态 SQL 与多表联查SSM 项目里 MyBatis 是数据层的核心。服装列表页需要按分类、风格、季节、颜色多条件筛选条件还不固定这时候动态 SQL 就派上用场了。select idselectClothingByCondition resultTypecom.campus.dress.entity.Clothing SELECT * FROM clothing where if testcategory ! null and category ! AND category #{category} /if if teststyle ! null and style ! AND style #{style} /if if testseason ! null and season ! AND season #{season} /if if testcolor ! null and color ! AND color LIKE CONCAT(%, #{color}, %) /if /where ORDER BY create_time DESC /select动态 SQL 记得加where标签而不是直接写WHERE 11前者的好处是能自动去掉多余 AND。另外查询搭配详情需要多表联查把 outfit 基本信息、outfit_item 明细、clothing 主体和标签一起取出来。我建议在 Mapper 里用嵌套结果集不要一条 SQL 拼好几十个字段那样性能和可读性都差。5.3 图片上传与静态资源映射服装系统必然有图片图片这个模块的坑比想象中多。我在 dev 环境直接把文件写到项目的 webapp/static/upload 下本地跑一切正常部署到 Tomcat 后就不行了因为上传目录在应用重启时会被清理。后来我换了一种做法在服务器上建一个独立的物理目录例如 /data/dress/upload然后通过 SpringMVC 的静态资源映射把虚拟路径 /upload/** 指向这个物理目录。mvc:resources mapping/upload/** locationfile:/data/dress/upload/ /同时把文件访问地址存到数据库的 image 字段里页面直接引用 /upload/xxx.jpg。这样应用重启不会丢图项目打包迁移也不受影响。这套思路和 Nginx 托管图片是同一个道理能懂这层写进项目说明里绝对加分。6. 我实测踩过的坑每个都能展开讲半小时这一部分是我最想分享的。很多毕设教程只会给你看成功截图不会告诉你失败的中间过程。这里把我真正踩过的几个问题完整写出来帮你提前避开。6.1 事务注解不生效自调用陷阱我在删除搭配功能里加了一个操作删除搭配主表记录同时删除明细记录。为了保证两个 delete 要么都成功、要么都失败我加了 Transactional。但测试发现删主表成功而删明细失败时主表的删除并没有回滚。排查了很长时间最后定位到原因Transactional 是通过 Spring AOP 代理实现的只有通过代理对象调用方法时事务才生效。我在同一个 Service 类内部用 this.deleteOutfit() 调用这里的 this 是原始对象不是代理对象所以注解没起作用。解决办法有两个第一个是拆分 Service把删除逻辑放到另一个 Service 类里在 Controller 层先调用 A 服务再调用 B 服务B 服务上的事务注解就能生效。第二个是我后来推荐的写法在类内部注入自身代理Autowired Lazy private OutfitService self; // 内部调用改为 self.deleteOutfitWithItems(outfitId);踩过这个坑之后我就养成了一个习惯凡是需要事务的代码一定确认是跨对象调用或者显式取得代理对象后再调。6.2 懒加载引发的 N1 查询问题最初显示搭配列表时我用的是先查出所有 outfit再循环查每条 outfit 的 outfit_item 和 clothing测试阶段数据量小没感觉后来往库里灌了三百多条测试数据列表接口响应时间从 20ms 飙到了 1.8 秒。原因就是 N1 查询——访问一次列表额外触发了 N 次明细查询。解决办法是加一个联表查询一次性把列表需要的字段都取出来select idselectOutfitWithClothingBatch resultMapOutfitVOMap SELECT o.id, o.name, o.cover_image, oi.clothing_id, c.name, c.category, c.color FROM outfit o LEFT JOIN outfit_item oi ON o.id oi.outfit_id LEFT JOIN clothing c ON oi.clothing_id c.id WHERE o.id IN foreach collectionoutfitIds itemid open( separator, close) #{id} /foreach /select然后在 Service 里把同一个 outfit 的明细聚合成列表。这一步优化做完接口响应时间恢复到 100ms 左右。我的体会是SSM 项目虽然小但数据库访问要时刻有批量思维永远不要循环里查库。6.3 中文乱码三个位置都要检查SSM 用 POST 提交表单时出现中文乱码网上资料说的最多的是在 web.xml 里配置 CharacterEncodingFilter这个我当然也配了但乱码依然存在。后来排查到另外两个地方一是 Tomcat 的 server.xml 里Connector 缺少 URIEncodingUTF-8导致 GET 请求传递中文参数乱码。二是 MySQL 连接串里面没有指定 characterEncodingutf8。光有这两处还不够数据库表本身的排序规则也必须是 utf8mb4_general_ci 或 utf8mb4_unicode_ci 才行。我的排查顺序建议是先看数据库表编码再看 JDBC 连接串最后看 Tomcat 配置。很多同学乱码查了半天其实只是数据库表建库时用了默认的 latin1。6.4 分页插件用不好反而更麻烦我一开始图省事用了 PageHelper但它在搭配列表这种多表联查的路由下会出现 count 语句拼接错误。后来我不依赖插件直接在 SQL 里手写 LIMITselect idselectOutfitPage resultTypecom.campus.dress.entity.Outfit SELECT * FROM outfit where if teststyle ! null and style ! AND style #{style} /if /where ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} /selectoffset 等于 (pageNum - 1) * pageSize在 Service 层计算好传入。手写 LIMIT 看起来土但逻辑完全可控不会被插件的动态代理干扰答辩时也更好解释。7. 答辩常见问题与扩展方向做完全套系统答辩才是临门一脚。这里把我自己经历过、以及帮学弟模拟时高频出现的问题整理出来提前备课能少很多紧张。7.1 导师可能会问的问题怎么应对为什么推荐算法选 UserCF 而不是 ItemCF你该怎么回答服装搭配场景里用户偏好非常个性化UserCF 侧重挖掘相似口味的人群能带来惊喜度ItemCF 更适用于内容强相关的场景比如图书在服装搭配里效果不明显。数据量很小协同过滤没有意义怎么办你该怎么回答承认稀疏性问题并说明规则引擎和热门推荐兜底这套组合策略在数据量小的阶段更实用。SSM 和 Spring Boot 的区别你该怎么回答SSM 需要手动装配 XML能帮助理解 Spring 底层原理Boot 是约定优于配置的产物开发效率更高但原理理解要求不同。如何保证推荐结果多样性你该怎么回答可以在排序时加入风格占比去重同一个风格最多推荐两套避免推荐结果全部是同一种风格。7.2 这套系统还能怎么扩展如果你的毕设要求更高或者想拿优秀毕设可以考虑这几个扩展方向把协同过滤和规则引擎的权重做成动态可调比如用 A/B 测试结果去调节参数。把穿搭维度的数据扩展到衣物颜色自动提取利用色彩分析工具判断搭配协调度。引入 Redis 缓存推荐结果并设置定时任务定期刷新减少实时计算的性能压力。前后端分离改造后端改为 Spring Boot前端用 Vue接口走 RESTful 风格。我当时给学弟的建议是优先保证核心推荐逻辑能讲清楚其次把工程细节异常处理、参数校验、统一返回结果做好这两块做好了普通毕设和优秀毕设的差距就已经拉开了。最后分享一个我自己实际操作中的体会做完这套系统再去面试 Java 开发岗聊到推荐业务时能够直接画出推荐链路图和写协同过滤公式面试官一般都会认为你有真实的项目经验。毕设不只是完成任务把项目里的每个决策都琢磨透它就会变成你简历上最有分量的项目经历。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑