简介基于SpringBoot框架与Java语言开发的一款个性化服装搭配推荐小程序毕业论文主要面向电子商务、软件工程等专业的毕业设计选题也适用于对个性化推荐系统在微信小程序端落地实践感兴趣的开发者。论文围绕传统电商推荐系统在服装搭配场景下难以满足用户个性化需求的问题提出基于用户偏好、商品信息、风格匹配及反馈优化的推荐方案并通过协同过滤算法生成搭配建议系统功能覆盖用户微信端与管理端包含用户管理、商品分类、风格标签、搭配反馈及订单联动等模块完整呈现了从需求分析、系统设计、代码实现到功能测试的毕业设计流程并对未来结合AR虚拟试穿技术增强用户体验进行了展望。资源包为单个docx文档大小2.25MB文档含中英文摘要、目录、正文及参考文献等完整毕业论文结构可直接作为论文撰写参照或项目开发蓝本。已有71人学习适合需要系统梳理个性化推荐落地思路并快速搭建同类项目的毕业设计学生与开发者学习参考。1. 个性化服装搭配推荐小程序一份能直接跑通的毕设论文工程Spring Boot做后端、微信小程序做端侧的个性化服装搭配推荐系统是这几年毕业设计里存在感很高的一类题目。这份论文工程最值得复现的地方是把“个性化推荐”这种听着很虚的功能落成了能跑的后端接口和能点的小程序页面数据库SQL、推荐接口、wx.login登录、首页加载更多整条链路是通的不是只有文档没有代码的半成品。它适合两类人一类是正在做同题或相近题目毕设、需要一套前后端工程作参照的另一类是手里有现成小程序项目、想快速给用户加一个“猜你喜欢”入口的。先把这套工程的骨架拆明白再往自己的项目里搬比从零拼接口省事得多。2. Spring Boot后端四张数据表与推荐接口的落地写法后端部分的核心不在Controller写了多少行而在数据模型怎么设计。推荐类项目最忌讳一上来就堆算法用户行为表都没建好算出来的权重全是空中楼阁。这套工程用了最经典的Spring Boot分层结构配合MyBatis-Plus操作数据库数据量在毕设这个级别完全够用而且代码量比纯MyBatis少一大截。2.1 分层结构为什么毕设建议用这套包骨架工程按controller、service、mapper、entity四层组织外加config和common两个辅助包。我刚接手时第一反应是“怎么这么规整”看完代码才发现这种规整是故意的推荐逻辑放service接口参数校验放controllerSQL操作全在mapper层后面要改推荐策略只动service一个文件就行。com.example.fashion ├── controller # 接收前端请求做参数校验 │ ├── AuthController.java │ ├── OutfitController.java │ └── UploadController.java ├── service # 业务逻辑包括推荐策略 │ ├── AuthService.java │ ├── OutfitService.java │ └── RecommendService.java ├── mapper # MyBatis-Plus 数据访问层 │ ├── UserMapper.java │ ├── ClothingMapper.java │ ├── OutfitMapper.java │ └── UserBehaviorMapper.java ├── entity # 数据库表对应的实体类 │ ├── User.java │ ├── Clothing.java │ ├── Outfit.java │ └── UserBehavior.java ├── config # 分页插件、跨域等配置 │ └── MybatisPlusConfig.java └── common # 统一返回结果、异常处理 ├── Result.java └── GlobalExceptionHandler.java各层职责很清晰Controller只做参数接收和结果包装Service写业务判断Mapper层用MyBatis-Plus的BaseMapper接口单表查询基本不用写SQL。common包里那个Result类建议保留前后端联调时统一返回结构能省掉大量“为什么这里多了一个data字段”的扯皮。MybatisPlusConfig里要注册分页插件否则后面推荐接口的分页参数不生效。常见做法是直接用PaginationInnerInterceptor配置数据库类型为MySQL这个不写的话page参数传了也白传。2.2 核心数据表把“搭配推荐”拆成四张表搭配推荐的核心是“用户-服装-行为”三者之间的关系。这套工程用了四张表来承载用户表、服装单品表、搭配主表、用户行为表。搭配和服装之间还有一张关联表因为一套搭配里有多件衣服一件衣服也可能出现在多套搭配里多对多关系必须用中间表拆开。-- 用户表 CREATE TABLE tb_user ( id bigint(20) NOT NULL AUTO_INCREMENT, openid varchar(64) NOT NULL COMMENT 微信openid, nickname varchar(50) DEFAULT COMMENT 昵称, avatar varchar(255) DEFAULT COMMENT 头像地址, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 服装单品表 CREATE TABLE tb_clothing ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 服装名称, category varchar(20) NOT NULL COMMENT 分类: top/bottom/shoes/accessory, style_tag varchar(100) DEFAULT COMMENT 风格标签, 逗号分隔: 休闲,通勤,运动, color varchar(30) DEFAULT COMMENT 主色调: 深蓝/米白/军绿, image_url varchar(255) NOT NULL COMMENT 图片地址, like_count int(11) DEFAULT 0 COMMENT 点赞数, 用于冷启动兜底, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 搭配推荐表 CREATE TABLE tb_outfit ( id bigint(20) NOT NULL AUTO_INCREMENT, title varchar(100) NOT NULL COMMENT 搭配标题, style_tag varchar(100) DEFAULT COMMENT 搭配风格: 通勤/约会/运动, cover_url varchar(255) NOT NULL COMMENT 搭配封面图, like_count int(11) DEFAULT 0 COMMENT 点赞数, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 搭配与单品的关联表 CREATE TABLE tb_outfit_item ( id bigint(20) NOT NULL AUTO_INCREMENT, outfit_id bigint(20) NOT NULL, clothing_id bigint(20) NOT NULL, sort_order int(11) DEFAULT 0 COMMENT 展示顺序, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 用户行为表 CREATE TABLE tb_user_behavior ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, target_type varchar(20) NOT NULL COMMENT 行为对象: outfit/clothing, target_id bigint(20) NOT NULL, behavior_type varchar(20) NOT NULL COMMENT 行为类型: view/collect/purchase, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;表设计里有两个细节值得注意。一是所有表都用utf8mb4而不是utf8因为微信昵称里经常出现emoji表情utf8存不了四个字节的字符真踩过这个坑的人都知道有多痛。二是openid加了唯一索引同一个微信号不会注册出两条用户记录省得每次登录都要查重。用户行为表是推荐算法的输入源。behavior_type区分了view浏览、collect收藏、purchase购买三类行为这三类行为在推荐时权重不一样购买权重最高收藏次之浏览最低。可以在Service里定义一个行为权重映射表比如view1、collect3、purchase5聚合用户风格偏好时按权重累加。2.3 推荐接口在Spring Boot里写出“根据用户偏好推搭配”推荐接口的入口很朴素一个GET请求带上userId和分页参数。核心逻辑在RecommendService里先查用户行为表聚合出偏好再按偏好风格召回搭配。新用户没有行为数据时走的是另一条路这个放到第5章单独讲。RestController RequestMapping(/api/outfit) public class OutfitController { Autowired private RecommendService recommendService; /** * 推荐搭配列表 * param userId 登录用户ID, 新用户传0 * param page 页码, 从1开始 * param size 每页条数, 默认10 */ GetMapping(/recommend) public Result recommend(RequestParam Long userId, RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size) { ListOutfitVO list recommendService.recommendWithColdStart(userId, page, size); return Result.ok(list); } }Controller层不写业务判断只做参数透传。RequestParam里的defaultValue很关键小程序端第一次加载首页时可能没传page默认值1保证接口不报错。Service public class RecommendService { Autowired private UserBehaviorMapper userBehaviorMapper; Autowired private OutfitMapper outfitMapper; public ListOutfitVO recommendWithColdStart(Long userId, int page, int size) { // 1. 取用户的行为风格偏好 ListString styleTags userBehaviorMapper.selectUserPreferredStyle(userId); // 2. 没有行为数据时走热门兜底 if (styleTags null || styleTags.isEmpty()) { return outfitMapper.selectHotOutfits(page, size); } // 3. 有偏好则按风格标签召回 return outfitMapper.selectByStyleTags(styleTags, page, size); } }selectUserPreferredStyle在Mapper里是一段SQL从行为表关联搭配表统计用户点赞和收藏的搭配里出现频率最高的风格标签。比如一个用户收藏过的搭配里“通勤”出现5次、“休闲”出现3次那这个用户就是典型的通勤风格偏好。这一步用SQL聚合一次搞定没必要在Java里循环统计数据库做聚合比应用层做快一个数量级。2.4 配置项application.yml中容易被忽略的四个参数工程能跑起来一半功劳在配置文件。我见过太多项目代码没问题、配置文件翻车的case下面这四个参数是这套工程里必须确认的。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/fashion_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0第一个是时区参数serverTimezoneAsia/Shanghai不写的话MySQL 8.x驱动会报时区错误或者在插入时间字段时差8小时小程序端显示的时间永远对不上。第二个是上传大小限制Spring Boot默认max-file-size只有1MB服装搭配的图片随便一张都是2MB起步不调大的话上传必然失败。第三个是map-underscore-to-camel-case: true数据库字段是create_time实体类是createTime这个配置开启后自动映射省掉一堆TableField注解。第四个是逻辑删除配置搭配表里的数据不建议物理删除用户收藏过的搭配如果被物理删了行为表里就留下了悬空引用。3. 小程序前端从登录态到“加载更多”的完整链路后端接口写得再完整小程序端对接不上也是白搭。这套工程的小程序端核心有三块网络请求封装、微信登录、首页推荐流的加载更多。把这三块跑通剩下的页面基本就是套模板。3.1 网络层封装统一处理token和错误码小程序里不能像Web端那样用axios所有请求都走wx.request。如果每个页面都直接调wx.requesttoken拼接、错误处理、loading状态会散落得到处都是。这套工程在utils目录下做了统一封装所有请求都走这一个方法。// utils/request.js const BASE_URL http://192.168.1.100:8080/api; function request({ url, method GET, data {} }) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method, data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success: (res) { if (res.statusCode 200 res.data.code 200) { resolve(res.data.data); } else if (res.statusCode 401) { // token过期, 清掉重新走登录流程 wx.removeStorageSync(token); wx.navigateTo({ url: /pages/login/login }); } else { wx.showToast({ title: res.data.msg || 请求失败, icon: none }); reject(res.data); } }, fail: (err) { wx.showToast({ title: 网络错误请检查后端是否启动, icon: none }); reject(err); } }); }); } module.exports { request };BASE_URL这里有个必须注意的细节不能在真机预览时写localhost。localhost在开发者工具里指向你的电脑但在手机上指向手机本身后端服务根本不在手机上。常见做法是开发时用电脑的局域网IP比如192.168.x.x真机预览时保证手机和电脑连同一个WiFi。请求封装里的401处理也值得留一下token过期后不弹一堆乱七八糟的报错而是静默清token并跳转登录页。3.2 wx.login到自定义登录code换token的完整链路微信小程序的登录不是直接用账号密码而是走wx.login拿一个临时code后端用这个code去微信服务器换openid。openid是用户在微信体系里的唯一标识但openid不能直接当token用因为任何人都能拿openid伪造身份正确做法是后端自己生成一个token返回给前端。// pages/login/login.js login() { wx.login({ success: async (res) { const code res.code; const resp await request({ url: /auth/login, method: POST, data: { code } }); wx.setStorageSync(token, resp.token); wx.setStorageSync(userId, resp.userId); wx.switchTab({ url: /pages/index/index }); } }); }// AuthController.java 核心逻辑 PostMapping(/login) public Result login(RequestBody LoginReq req) { // 1. 用code换openid String url https://api.weixin.qq.com/sns/jscode2session?appid appid secret secret js_code req.getCode() grant_typeauthorization_code; String result restTemplate.getForObject(url, String.class); JSONObject json JSONObject.parseObject(result); String openid json.getString(openid); // 2. openid查库, 不存在就注册新用户 User user userMapper.selectByOpenid(openid); if (user null) { user new User(); user.setOpenid(openid); userMapper.insert(user); } // 3. 生成自己的token返回给前端 String token UUID.randomUUID().toString().replace(-, ); return Result.ok(new LoginResp(token, user.getId())); }微信服务器返回的json里有一个session_key字段注意这个字段不要返回给前端它是用来解密用户手机号等敏感信息的泄露出去有安全风险。后端自己生成token后后续请求带上token后端就可以在拦截器里解析出用户身份不需要每次都调微信接口。3.3 首页推荐流列表渲染与触底加载更多首页推荐流用的是scroll-view组件的bindscrolltolower事件滚动到底部自动加载下一页。这里有个常见的翻车点直接把所有数据一次性渲染出来数据量大了页面卡成PPT。正确的做法是分页加载每页10条滚动到底部再拉下一页。!-- pages/index/index.wxml -- scroll-view classlist scroll-y bindscrolltolowerloadMore view classcard wx:for{{outfits}} wx:keyid image src{{item.coverUrl}} modeaspectFill/image view classcard-info text classtitle{{item.title}}/text text classtag{{item.styleTag}}/text /view /view view wx:if{{loading}} classloading加载中.../view view wx:if{{!hasMore outfits.length 0}} classno-more没有更多了/view /scroll-view// pages/index/index.js data: { outfits: [], page: 1, size: 10, hasMore: true, loading: false }, loadMore() { if (!this.data.hasMore || this.data.loading) return; this.setData({ loading: true }); request({ url: /outfit/recommend, data: { userId: wx.getStorageSync(userId), page: this.data.page, size: this.data.size } }).then((list) { this.setData({ outfits: this.data.outfits.concat(list), page: this.data.page 1, hasMore: list.length this.data.size, loading: false }); }); }加载更多的判断逻辑是hasMore: list.length this.data.size意思是这次返回的数量等于请求的数量那很可能还有下一页。如果返回不足10条说明数据库里的数据已经拉完了hasMore置为false底部显示“没有更多了”同时阻止后续的加载请求。loading标志位是防止用户快速滚动时连续触发多次请求的这个一定要加不然会出现数据重复。3.4 图片加载不出来域名白名单与抓包排查小程序里图片加载不出来一半以上的原因不是代码问题是域名配置问题。开发模式下在开发者工具右上角“详情-本地设置”里勾选“不校验合法域名”图片和请求都能正常发出去。但真机预览和正式上线时微信强制要求所有请求域名和图片域名都必须在小程序后台配置白名单而且request合法域名和downloadFile合法域名是分开配置的图片用image组件加载走的是downloadFile那条漏配一个就加载不出来。排查这类问题时我一般会开抓包工具看请求到底发出去没有。用Charles抓一下小程序的HTTPS请求如果看到图片请求返回了403或域名错误基本就是白名单漏配如果请求压根没发出那是业务逻辑的问题。小程序端的网络请求在开发者工具的Network面板里也能看到但真机上出了问题就只能靠抓包Charles抓小程序的流程是手机设置代理指向电脑的Charles监听端口装好Charles证书后就能看到小程序发出的所有HTTPS请求包括图片、接口、上传文件。4. 踩坑与排查五个最容易翻车的位置这套工程我从导入到跑通前后折腾了一天半。一半的时间花在代码逻辑上另一半全在填环境配置的坑。下面五个问题是我实际遇到过、而且反复出现的按概率排序先排查这些能省下大量时间。4.1 小程序请求直接失败本地后端连不上现象wx.request走fail回调提示信息是request:fail后端控制台一条日志都没有。原因两种情况最常见。一是开发者工具没勾选“不校验合法域名”小程序默认拒绝向非HTTPS域名发请求二是BASE_URL写成了localhost真机预览时localhost指向的是手机自己。解决开发工具里勾选“不校验合法域名”BASE_URL改成电脑的局域网IP手机和电脑连同一个WiFi。如果用的是云开发或线上服务器确认域名是HTTPS且在后台配了白名单。后端启动后先自己在浏览器里访问一下接口确认服务真的起来了再做前端排查。4.2 登录接口报错code2session返回40029现象后端调用微信接口返回{errcode:40029,errmsg:invalid code}。原因微信的code是一次性的用一次就失效。前端每调一次wx.login就会刷新code如果旧code还在请求队列里没发出去或者前端代码里一次性用了两次第二个请求拿到的code就废了。另外AppID和AppSecret不匹配也会报这个错把测试号的AppID配到了正式的secret上。解决前端确保每次登录流程只调一次wx.login拿到code立刻传给后端后端在日志里打AppID和secret的脱敏值核对是不是同一套小程序的。这个错是我排查最久的最后发现是复制配置文件时把secret多复制了一位。4.3 Spring Boot版本太高JDK版本对不上现象用IDEA启动项目直接报UnsupportedClassVersionError或者Maven编译时报错无法解析Spring Boot依赖。原因Spring Boot 3.x要求JDK 17以上而很多毕设模板和电脑上的默认JDK还是8版本对不上直接启动不了。反过来说如果用了JDK 17却引用了Spring Boot 2.x的某些旧依赖也会因为兼容性问题报错。解决先跑java -version确认当前JDK版本再看pom.xml里Spring Boot的parent版本。Spring Boot 3.x就配JDK 17Spring Boot 2.7.x就配JDK 8不要混。IDEA里可以在Project Structure里给这个Module单独指定JDK版本不用全局改。4.4 上传服装图片老是失败现象上传进度条走到一半就断后端日志报MaxUploadSizeExceededException或413 Request Entity Too Large。原因Spring Boot内置的Tomcat默认单文件上传上限是1MB服装图片随便一张手机拍的都是2MB以上必超。这个默认值经常被人忽略因为本地小图测试没问题一旦上真机拍的照片就翻车。解决在application.yml里把max-file-size改成10MBmax-request-size改成20MB改完必须重启应用。如果用的是Nginx反向代理还要检查Nginx的client_max_body_size是不是也限制了。4.5 真机canvas生成的搭配图模糊现象开发者工具里canvas生成的分享图很清晰一到iPhone上就模糊文字边缘发虚。原因canvas在真机上按物理像素渲染而开发者工具里按CSS像素渲染。设计稿宽度375px的图在iPhone的2倍屏上只有187.5个物理像素不糊才怪。解决绘制前用wx.getSystemInfoSync()拿设备pixelRatio把canvas的宽高乘以pixelRatio再通过scale放大绘制。代码上就是在canvas的初始化阶段多写三行const dpr wx.getSystemInfoSync().pixelRatio; canvas.width width * dpr; ctx.scale(dpr, dpr);。这个坑属于不做真机测试绝对发现不了的血泪经验。5. 进阶先别急着上算法把冷启动兜住推荐接口能返回数据了但还有一个隐藏问题新用户第一次打开小程序行为表是空的没有浏览记录、没有收藏、没有购买按用户偏好去查搭配查出来的列表是空的首页直接白屏。这是推荐系统里的冷启动问题也是这套工程最值得改的一个点。最本文还有配套的精品资源点击获取