ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Spring Boot的一周穿搭App实战:从选题到部署全流程

基于Spring Boot的一周穿搭App实战:从选题到部署全流程 又是一年课题季。每年这个时候都有学生朋友拿着类似基于Spring Boot的XX系统的设计与实现的课题来找我讨论今年让我印象比较深的是这个一周穿搭App。它表面上只是个普通的CRUD项目但仔细拆解之后会发现从服装库管理到每日穿搭推荐再到天气联动和一周视图里面藏着不少值得深挖的设计点。这篇文章我就以这个课题为主线把我从选题分析、技术选型、数据库建模到最终联调部署的完整过程都写出来包括那些不写进论文里、但实际动手必踩的坑希望能给你一条可以直接参考复现的路线。1. 这个一周穿搭课题到底在做一个什么系统1.1 从课题名称拆解核心痛点一周穿搭App这个名字听起来很生活化但它的业务内核其实很清晰解决每天早上不知道穿什么这个高频痛点。用户把衣柜里的衣服录入系统给每件衣服打上标签类型、颜色、适用温度区间、风格然后系统根据当天天气、用户历史穿搭记录和评分给出今天的推荐搭配同时支持用户按星期规划一周的穿搭提前排布。如果你在写开题报告或者需求分析建议把业务痛点用一句话写明白从站在衣柜前纠结变成打开App直接抄答案。这个一句话需求会成为你后面所有功能设计的锚点遇到拿不准要不要做的功能就回头对照这句话。1.2 功能边界划定做有用而非大而全很多同学一上来就想着做社交、做电商、做AI试衣我必须拦一下。课题设计的核心评价标准是有完整业务闭环不是功能数量。我建议划定这样的功能边界模块必做功能选做/加分功能用户管理注册、登录、JWT鉴权、个人信息维护头像上传、密码找回服装库衣服增删改查、按分类/季节筛选、图片上传按颜色/标签多条件组合筛选搭配管理每日穿搭记录日期唯一、评分、备注按星期批量预排一周穿搭穿搭推荐基于天气温度和服装标签的匹配推荐基于历史评分的热门搭配排序天气联动按城市获取当日天气和温度区间未来3天天气趋势展示数据统计每周穿搭次数统计月度风格偏好柱状图这个表格你要是能写进需求文档里导师一眼就能看出你想清楚了。一定牢记先把闭环跑通再谈花活。没有评分统计的穿搭记录系统、没有天气联动的推荐逻辑都只是换皮CRUD答辩时很容易被问倒。2. 技术选型逻辑为什么锁死Spring Boot 2.7.x而不是3.x2.1 版本选择是这个课题的第一个分水岭关于Spring Boot版本我看到有些同学用的是3.x然后用起来发现各种不顺手。这里我给出一个非常实用的建议做这类课题项目直接锁定Spring Boot 2.7.x不要上3.x。原因有三点。第一是环境兼容性。Spring Boot 3.x最低要求JDK 17而很多实验室电脑和答辩环境还停留在JDK 8。你千辛万苦在JDK 17上写完代码部署到老师的机器上报UnsupportedClassVersionError这画面我见过太多次了。Spring Boot 2.7.x配合JDK 8非常稳定也完全够用。第二是生态兼容性。这是最关键的痛点。Spring Boot 3.x从javax.*迁移到了jakarta.*命名空间意味着大量老牌第三方库不兼容。你查资料时搜到的大部分博客、CSDN帖子还是基于javax写的照着抄代码直接编译报错。比如整合MyBatis-Plus、PageHelper这些常用组件2.7.x生态下的稳定版本一搜一堆3.x下还要去专门找适配版本纯属给自己加戏。第三是课题验收的稳妥性。课题项目追求的是逻辑清晰、能跑能演示不是追求最新版本。导师问起来为什么用2.7.18你可以理直气壮地说为了保证框架生态的稳定兼容性降低项目风险这本身就是加分项。2.2 前后端架构App端到底用什么方案有同学纠结App怎么实现。这里要明确课题里说的App不一定非要用原生Android或Swift去写。实际上最稳妥的方案是H5移动端 WebView壳或者干脆做一个移动端样式的前端网页在浏览器里用模拟器模式演示也完全符合App设计与实现的课题要求。我推荐两套路径你根据自己的前端基础选路径A推荐给前端基础薄弱纯Thymeleaf模板引擎服务端渲染。好处是一个Spring Boot工程搞定所有不用解决前后端分离的跨域问题打包部署也简单。缺点是页面交互弱一点但做管理后台和穿搭列表完全够用。路径B推荐给有Vue基础前后端分离Vue 3 Vite 构建移动端H5页面。你搜到的vue打包放进springboot就是这条路线的核心操作Vue项目npm run build之后把dist目录拷到Spring Boot的src/main/resources/static下这样前端页面和后端接口就部署在同一个服务里既享受了前后端分离的开发体验又避免了线上还要部署Nginx的麻烦。移动端适配用viewportrem就能做出看起来像个App的效果。我个人建议你走路径B。理由很简单Vue做的页面美观度高答辩演示效果远好于Thymeleaf的朴素页面而且基于Spring Boot Vue在课题描述里也更好看。2.3 持久层选型MyBatis-Plus依然是效率之王持久层我不太建议用原生MyBatis也不想让你去研究JPA的复杂映射。直接选MyBatis-Plus 3.5.x原因是它的BaseMapper自带增删改查和分页方法你写穿搭记录、服装库这种单表CRUD几乎不用手写SQL能把精力放在业务逻辑上。搭配MybatisPlusInterceptor配置分页插件接口层配合Page对象列表分页五分钟搞定。你可能会问为什么不选Spring Data JPA。JPA在关联查询和动态条件拼接上反而更绕——比如按温度区间风格标签颜色组合筛选衣服JPA需要写SpecificationMyBatis-Plus用LambdaQueryWrapper几行代码就解决了。实际开发中MyBatis-Plus的中文文档和示例代码丰富度也远高于JPA出了问题好查。3. 数据库建模的核心日期唯一约束和标签辐射3.1 核心表设计不要一上来就设计七八张表你打开Navicat之前先想清楚一件事这个系统的核心关系是什么无非是用户拥有衣服用户每天记录穿搭衣服有标签。围绕这个核心我最终落地了五张表-- 用户表 CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL, password varchar(100) NOT NULL COMMENT BCrypt加密存储, nickname varchar(50) DEFAULT NULL, city varchar(50) DEFAULT NULL COMMENT 默认城市用于天气查询, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 服装库表 CREATE TABLE clothing_item ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, name varchar(100) NOT NULL COMMENT 如藏蓝色呢子大衣, category varchar(20) NOT NULL COMMENT 上装/下装/外套/鞋履, color varchar(20) DEFAULT NULL, style_tag varchar(100) DEFAULT NULL COMMENT 标签如通勤,休闲,运动, min_temp int(11) DEFAULT NULL COMMENT 适用最低温度, max_temp int(11) DEFAULT NULL COMMENT 适用最高温度, image_url varchar(200) DEFAULT NULL, status tinyint(4) DEFAULT 0 COMMENT 0闲置 1常用 2清洗中, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_category (user_id,category) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 每日穿搭记录表核心表 CREATE TABLE outfit_record ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, record_date date NOT NULL COMMENT 哪一天穿了什么, weather_info varchar(50) DEFAULT NULL COMMENT 当时天气冗余存储, temperature int(11) DEFAULT NULL COMMENT 当时温度冗余存储, rating tinyint(4) DEFAULT NULL COMMENT 评分1-5可为空, comment varchar(255) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_date (user_id,record_date) COMMENT 一人一天只能有一条记录 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 穿搭明细表记录一套穿搭里包含哪些衣服 CREATE TABLE outfit_detail ( id bigint(20) NOT NULL AUTO_INCREMENT, outfit_id bigint(20) NOT NULL, clothing_item_id bigint(20) NOT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这套设计的精妙之处在于outfit_record和outfit_detail分离。为什么单独拆一张明细表因为一套穿搭包含上装、下装、外套等多件衣服如果都塞在outfit_record里就要设计固定字段top_id、bottom_id、coat_id以后想加帽子、围巾怎么办改表结构明细表让你加任何单品都无需改表这就是标准的一对多建模。3.2 按周查看的查询路径索引和日期边界一周穿搭最核心的交互是日历视图周一到周日每天显示一套穿搭缩略。对应SQL其实非常简单难在设计时就想到索引SELECT * FROM outfit_record WHERE user_id #{userId} AND record_date BETWEEN #{startDate} AND #{endDate} ORDER BY record_date ASC;配合uk_user_date这个唯一索引这一条查询在数据量只有几百条的情况下性能完全够用。我特别提醒record_date不要用字符串要用date类型这样MySQL才能走索引高效范围查询。好多同学喜欢图省事存2026-03-09字符串一旦需要按月份统计就要写一堆DATE_FORMAT转换函数性能直接退化。3.3 不少初学者会忽略的点状态字段和冗余字段上面表里的status字段和weather_info字段看着不起眼实际暗含设计考量。status字段解决的是这件衣服已经送去干洗了不要推荐的场景没有这个字段你的推荐算法会把所有衣服都拉出来体验很差。weather_info和temperature在outfit_record里冗余存储是为了让历史穿搭记录自解释——你回看3月9日穿了什么时直接能看到当天天气不用再反查天气服务。虽然违背了一点规范化原则但换来了查询效率和快照语义实际项目中这种冗余是被鼓励的。4. 后端实现的重头戏穿搭推荐与天气联动4.1 天气接入的轻量方案和容错设计一周穿搭App比较关键的一个接口是天气查询。不要自己解析什么气象数据直接选一个天气API服务商和风天气、高德天气都行。我以高德天气为例它只需要一个API Key通过城市编码查询实时天气// 服务类核心方法 public WeatherInfo getWeather(String city) { // 1. 用城市名调用高德地理编码接口拿到adcode城市编码 // 2. 用adcode调用天气接口拿到实况数据 String url https://restapi.amap.com/v3/weather/weatherInfo? key weatherApiKey city URLEncoder.encode(adCode, UTF-8) extensionsbase; // 解析JSON读取temp温度和weather天气现象 }真正上线时你会遇到三个问题我在本地测试也全部踩过第一是城市名模糊匹配。用户填上海没问题填上海市地理编码也能识别但填shanghai就挂了。所以入库时最好统一转成标准的城市名或者直接用省市两级联动下拉框从源头避免用户乱填。第二是接口限流。免费版天气API每分钟调用次数有限你如果每次刷新首页都调一次天气很快就触发限流。我的方案是加一层本地缓存把天气查询结果放到一个HashMap里key是城市value是带时间戳的天气数据5分钟内复用。配置类里加ConfigurationProperties读取缓存时长别写死。第三是接口挂了怎么办。这是最重要的容错设计。天气服务偶尔超时或返回error你的App不能跟着白屏。我的兜底逻辑是try-catch捕获异常后直接返回昨天或者前天的天气快照从outfit_record里取最近的weather_info如果没有历史数据就返回一个默认温度区间让推荐逻辑照常运转。演示时如果现场网络抽风你照样能展示完整的推荐流程这个细节答辩时非常加分。4.2 推荐算法从一个朴素但完整的评分模型说起很多课题里都有智能推荐这种字眼但我被问过最多的问题是算法太简单怎么办。这里我负责任地告诉你对于一周穿搭这种体量的系统你不需要上机器学习一个基于规则的评分排序模型就足够出彩。关键在于把推荐逻辑拆成清晰的步骤每一步都有据可依。我的推荐流程分三步第一步按温度过滤。拿到当天温度后从衣服表里查出min_temp 当前温度 max_temp的衣服这是硬性筛选。没有合适合适温度区间的衣服直接淘汰避免出现6月推荐羽绒服的灾难场景。第二步按标签打分。给衣服的style_tag设置权重比如当天气温低于10度标签含保暖的3分含羊毛的2分气温高于26度透气3分棉麻2分。这些权重我放在一张配置表里而不是写死在Java代码中方便答辩时展示规则可配置。第三步随机扰动去重。前两步的结果如果每次都一样用户连续看三天推荐会发现永远是同一套衣服体验很糟糕。我引入一个Random因子综合分相同时做一次随机排序并在推荐结果里排除近3天已经穿过的衣服排除逻辑用一条SQL查询近3天outfit_detail里的clothing_item_id然后NOT IN。下面贴一段核心推荐代码的骨架public ListClothingItem recommendOutfit(Long userId, Integer temperature, String weather) { // 1. 温度区间硬过滤 ListClothingItem candidates clothingItemMapper.selectList( Wrappers.ClothingItemlambdaQuery() .eq(ClothingItem::getUserId, userId) .ne(ClothingItem::getStatus, 2) // 排除清洗中 .le(ClothingItem::getMinTemp, temperature) .ge(ClothingItem::getMaxTemp, temperature)); // 2. 排除近3天已穿过 ListLong recentIds outfitDetailMapper.findWornItemIds(userId, 3); candidates.removeIf(item - recentIds.contains(item.getId())); // 3. 标签评分 随机扰动 candidates.forEach(item - { int score tagScoreCalculator.calculate(item, temperature, weather); item.setScore(score randomHelper.nextInt(3)); }); // 4. 按分数降序上装/下装/外套分层返回 return candidates.stream() .sorted(Comparator.comparingInt(ClothingItem::getScore).reversed()) .collect(Collectors.toList()); }你答辩的时候重点不是讲代码本身而是讲清楚三个设计意图为什么先硬过滤再打分减少无效计算、为什么排除近3天增加推荐多样性、为什么加随机因子提升用户体验。这比堆砌任何复杂公式都有说服力。4.3 穿搭日历的批量预排功能一周穿搭里的周视图如果只支持每天单独记录效率太低。我实现了批量预排用户可以在周日晚上一键为下一周的7天生成穿搭草稿。生成逻辑也不复杂——把推荐接口按7天的温度预报各调一遍每天生成一套组合存进outfit_record里状态标记为草稿rating为空comment为空就是草稿用户每天可以编辑调整。这一步实现时有个细节uk_user_date唯一索引会让你批量插入时因为某一天已存在记录而整体失败。解决方案是使用INSERT ... ON DUPLICATE KEY UPDATE或者saveBatch时捕获DuplicateKeyException逐条跳过。我用的是逐条插入前先查重虽然多一次查询但逻辑最清晰代码也好解释。5. 联调与部署里那些不写进论文的坑5.1 跨域这只拦路虎以及CORS的隐藏冲突如果你走Vue前后端分离路线第一个遇到的就是跨域。开发环境下Vue跑在5173端口后端跑在8080端口前端fetch请求后端必然跨域。解决方案是在Spring Boot里写一个全局CORS配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(Registry registry) { registry.addMapping(/api/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }这里有一个网上搜不到细节的坑如果你使用.allowedOrigins(*)同时开启了.allowCredentials(true)Spring Boot 2.7会直接启动报错安全校验会拒绝这种组合。网上的旧教程让用这两个搭在一起就会踩这个坑。正确写法就是上面代码里的.allowedOriginPatterns(*)它专门解决通配符来源携带凭证的兼容问题。这个坑在我第一次整合时卡了我整整一个下午现在写出来你就绕过去了。5.2 图片上传本地目录和Nginx静态映射的取舍服装图片上传也是课题里躲不开的功能。最简单的方案是上传后存在本地磁盘的一个目录比如D:/upload/然后通过配置类映射为静态资源访问Configuration public class WebConfig implements WebMvcConfigurer { Value(${upload.path}) private String uploadPath; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 把本地磁盘目录映射到 /images/** 路径 registry.addResourceHandler(/images/**) .addResourceLocations(file: uploadPath /); } }配置好了之后前端访问http://localhost:8080/images/xxx.jpg就能看到图片。但有两个坑必须提醒第一Tomcat默认单次请求最大2MB你需要给SpringApplication加一下配置spring.servlet.multipart.max-file-size5MB spring.servlet.multipart.max-request-size10MB否则前端一传超过2MB的图片报错报得莫名其妙。第二图片文件不能放在打包后的Jar包内部目录否则部署到服务器重启后图片全部丢失。用本地绝对路径是安全稳妥的做法。如果项目要交到服务器上演示可以再加一个操作上传成功后把D:/upload/xxx.jpg的图片复制到Nginx的静态目录下由Nginx直接返回。两个方案选一个就行答辩时能讲清楚你选型的理由就够。5.3 答辩演示前的最后自检清单项目写完了验证环节经常被忽略导致答辩现场翻车的案例每年都有。我建议你按这个清单逐项确认手机和电脑连的是同一个Wi-Fi访问的是http://局域网IP:8080而不是localhost。别笑每年都有学生只配了localhost最后PPT投放电脑访问不到。用Postman把所有接口过一遍确认增删改查的返回JSON结构统一建议统一为{code, message, data}格式前端解析才不会出错。登录过期逻辑测一遍JWT过期后前端要跳回登录页不要出现白屏或者无限loading。演示时尽量用测试账号提前录好10件以上的衣服和3天的穿搭记录演示是有故事的从录入衣服到查看今日推荐到编辑穿搭打分一气呵成。准备一个异常演示预案比如把天气服务关掉展示App如何兜底降级。这么做不是自找麻烦而是主动展示你的容错设计绝对是答辩的高光时刻。如果你打算把Vue打包放进Spring Boot里再补充一点npm run build后生成的dist目录内容直接粘贴到src/main/resources/static里但注意让后端接口路径统一加/api前缀这样才能避免静态资源路径和接口路径冲突。打包时如果遇到history路由刷新404说明Vue路由用了history模式改成hash模式或者给Spring Boot配一个跳过静态资源的转发即可。最后再分享一个我的个人习惯给outfit_record表的comment字段留好扩展位很多二期的想法——比如周五晚上有约会想看正式一点的搭配、通过记录用户调研反馈和偏好标签来猜你想穿——都是从这个字段里生长出来的。一个课题项目的深度往往不体现在用了多少技术栈而在这些细节设计里。你把这个闭环跑通吃透每一个设计决策背后的理由答辩的时候一定是全场最从容的那个。
返回列表