从去年开始陆续有准备做毕设的同学来问我能不能推荐一个“有意思但不至于做不完”的题目。商城、图书管理、课程表这些确实太多了答辩时老师看一眼题目就没有新鲜感。我当时给出的建议是试试做一个高校汉服租赁网站管理平台技术栈直接用SpringBootVue数据库用MySQL走Java方向的全栈路线。这个题目最妙的地方在于汉服租赁和普通商品交易有本质区别——它存在“同一件衣服、同一尺码在某些日期被租走之后就不能再租”的双重约束。这个特性让项目天然具备比普通CRUD更深的业务逻辑但又不会复杂到脱离毕设可控范围。你既能展示全栈开发能力又能讲清楚一套完整的订单状态机、库存与时间维度的关联设计这在答辩时是非常好的亮点。接下来我把这个项目的完整拆解和实操过程写出来从需求边界、数据库设计、后端接口、前端页面到部署排错和答辩准备按我实际帮几位同学搭项目的顺序来能直接照着复现。1. 汉服租赁的独特业务痛点为什么值得做一个独立系统1.1 租赁场景和普通商城最大的差异在哪里如果只是做一个“汉服商城”那本质上就是商品表加订单表和卖书卖衣服没区别。租赁不同核心差异有三个。第一库存是按时间维度消耗的。普通商品卖一件少一件库存是静态数字。汉服租赁场景下一套衣服明天可以租后天可能已经被别人预定了同一件衣服在同一时间段内只能存在一个有效订单。这意味着系统不仅要记录“剩多少件”还要记录“哪些日期还能租”。第二尺码维度不可忽略。汉服虽然是均码居多但实际租赁中还是有S、M、L、XL之分甚至有些店铺会提供“加长款”。如果不区分尺码用户下单选“大码”却发现库存不够体验就很差。合理的做法是每种衣服按尺码维护独立库存。第三订单生命周期特别长。普通购物订单从付款到收货基本就结束了。租赁订单至少要经历“提交→支付→确认取件→租赁中→归还→确认完成”这几个阶段中间任意一步都可能出现逾期、损坏、取消。这就逼着你必须设计一个清晰的状态机而不是简单用一个int字段存订单状态。1.2 功能边界一套“够用”的租赁系统包含哪些模块我给A同学规划第一版时明确把系统拆成用户端、管理员端、公共基础三块。每一块的功能都以“能把租赁闭环跑通”为准不做多余的花活。用户端包含注册登录、首页汉服展示与轮播、分类筛选形制、朝代风格、适用场合、商品详情页图册、尺码选择、日期选择、价格计算、购物车、订单提交与支付演示用模拟支付即可、个人中心我的订单、退订/取消、评价。管理员端包含商品管理增删改查、上下架、库存维护、分类管理、订单管理核心是状态推进确认、取消、标记归还、用户管理、简单的数据统计订单量、热门商品Top5。公共部分就是统一的登录鉴权和角色权限。管理员和普通用户共用一张用户表用一个role字段区分接口层面做拦截。第一版不要碰拼团、优惠券、消息推送这些低频功能。先把租赁主链路跑通代码量和答辩讲稿都好控制。1.3 从毕设/课设角度评估这个题目的性价比这题的性价比体现在两个方向。对课程设计来说去掉管理端的数据统计保留完整的租赁下单流程工作量和一门JavaWeb课程的大作业匹配度很高。对毕业设计来说加上管理端、统一鉴权、状态机设计、并发扣库存的思考整个项目的技术深度足够支撑一篇不错的论文。更重要的是这题目特别好演示。答辩现场你可以从用户注册开始模拟下单租一套汉服然后切管理员视角确认订单、标记归还整个过程是完整的业务闭环。相比“管理系统的增删改查”这种演示租赁系统的流程感强得多老师一眼就能看出你做了真实的设计。2. 技术栈选型SpringBootVueMySQL这个组合为什么合适2.1 三个核心组件各自承担什么角色这个项目本质上是典型的前后端分离架构。SpringBoot负责提供RESTful API处理业务逻辑、权限校验、数据库读写Vue负责前端界面渲染和用户交互通过axios调用后端接口MySQL负责数据的持久化存储。我见过很多同学纠结要不要引入Redis、要不要上Spring Cloud我的建议是毕设场景不要过度设计。SpringBootVueMySQL是已经被验证过无数次的稳妥组合资料最多、报错最容易搜到方案、答辩老师也最熟悉。你不是在做企业级高并发系统而是在证明自己掌握了全栈开发的核心能力这就够了。2.2 依赖版本怎么选避免自己给自己挖坑版本选择是很多新手项目翻车的重灾区。我推荐一套我实际跑通过且相对稳妥的版本组合技术组件推荐版本说明JDK1.8 或 11学校机房和老服务器兼容性最好JDK17也能跑但没必要冒险SpringBoot2.7.x非常成熟的版本网上踩坑资料最全MyBatis-Plus3.5.x省掉大量单表CRUD的XML自带分页插件MySQL5.7 或 8.0两种都可以注意驱动和连接串差异VueVue2 Element UI如果你Vue3更熟用Vue3 Element Plus也没问题两者思路一致构建工具Maven 3.6 / npm 8后端前端标准构建工具特别提醒一点SpringBoot 2.7.x 对应的是 javax.servlet 命名空间SpringBoot 3.x 换成了 jakarta网上大量旧教程的代码在3.x上直接编译不过。如果你跟着视频做一定先确认对方的Boot版本别混着看。2.3 前后端目录结构从一开始就分好层后端我习惯按这种结构分包代码量不大但层次清晰答辩时也好讲src/main/java/com/example/rental/ ├── controller/ # 接口层只做参数接收和结果返回 ├── service/ # 业务层订单流转、库存扣减等核心逻辑 ├── mapper/ # MyBatis-Plus的Mapper接口 ├── entity/ # 数据库实体类 ├── dto/ # 请求参数封装比如下单参数 ├── vo/ # 响应结果封装比如订单详情 ├── config/ # 跨域配置、MyBatis-Plus分页配置、拦截器配置 ├── common/ # 统一返回结果、异常处理、状态枚举、常量 └── utils/ # JWT工具类等前端Vue项目用官方脚手架创建后在src下单独建views、router、store、api、components这几个目录。views按页面划分如Home.vue、ProductList.vue、ProductDetail.vue、Cart.vue、OrderConfirm.vue、UserOrders.vue、Login.vue管理端单独建admin目录放AdminProduct.vue、AdminOrder.vue等。这样的结构看起来平平无奇但就是这种规整的划分能让阅读你代码的人包括答辩老师在第一次看到项目时快速建立认知。很多同学项目做完了一个Controller里写了上千行业务逻辑这种代码即使能跑答辩也容易被追问到崩溃。3. 数据库设计库存、订单、状态流转是三个核心战场3.1 核心数据表清单与字段设计这个系统的表数量其实不多第一版我设计了8张表用户表、分类表、汉服商品表、商品库存表按尺码、购物车表、订单表、订单明细表、评价表。下面把核心字段列出来。用户表字段名类型说明idbigint主键usernamevarchar(50)登录名passwordvarchar(100)BCrypt加密后的密码real_namevarchar(50)真实姓名涉及押金信用phonevarchar(20)手机号roletinyint1普通用户2管理员avatarvarchar(255)头像路径create_timedatetime注册时间汉服商品表字段名类型说明idbigint主键category_idbigint所属分类namevarchar(100)例如“唐制齐胸襦裙”cover_imagevarchar(255)封面图imagestext多图JSON或逗号分隔descriptiontext商品描述price_per_daydecimal(10,2)每日租金单独设“押金”也行depositdecimal(10,2)押金金额statustinyint1上架0下架salesint累计租出次数create_timedatetime上架时间商品库存表是一个容易被人忽略但非常关键的设计字段名类型说明idbigint主键clothing_idbigint关联商品sizevarchar(20)尺码S/M/L/XLtotal_countint该尺码总库存为什么不直接在商品表里放一个库存字段因为不同尺码库存不同。你把库存放主表下单时还是要按尺码查询判断等于多一次依赖关系。独立库存表之后每次下单要做的检查很直接查clothing_stock表当前尺码剩余量是否足够。订单主表字段名类型说明idbigint主键order_novarchar(32)订单编号唯一user_idbigint下单用户total_pricedecimal(10,2)租金总额depositdecimal(10,2)押金总额statustinyint订单状态见下文枚举start_datedate租赁开始日期end_datedate租赁结束日期rent_daysint租赁天数create_timedatetime下单时间update_timedatetime最近更新时间订单明细表order_id、clothing_id、clothing_name冗余快照、size、quantity、price_per_day、subtotal。明细表的意义是保留下单那一刻的商品快照。商品信息后续可能改价、改名但订单历史里应该看到的是下单时的原始数据这也是一个答辩可以讲的细节。3.2 订单状态机的设计一张表回答所有“谁触发下一步”订单状态是整个系统逻辑最集中的地方。我第一版定义了下面几种状态状态值名称含义1待支付用户已下单尚未模拟支付2已支付/待取件支付完成等待用户线下自取或等待管理员确认3租赁中已确认取件租期开始4已归还用户归还等待管理员确认5已完成管理员确认归还订单结束6已取消用户取消或超时未支付自动取消7已逾期超出end_date仍未归还由系统定时任务扫描标记状态流转必须限制方向。比如“待支付”只能流向“已支付”或“已取消”“租赁中”不能直接跳到“已完成”“已归还”必须是中间态。我在service层会写一个状态流转校验方法每次更新状态前先判断当前状态是否允许跳到目标状态。这是答辩时非常容易讲清楚的设计点。逾期状态可以有两种实现方式一是用户归还时前端预判和提醒二是系统里用一个Spring定时任务每天凌晨扫描一次把end_date小于当天且status3的订单标记为逾期。第二版可以直接加。这个设计也顺带展示了你的任务调度能力写起来就是几百行的代码量。3.3 租期与费用计算把逻辑前置到下单时刻租金的计算公式很简单总租金 单价 × 天数天数是结束日期减去开始日期。这里有一个容易踩坑的点日期计算的边界。比如用户选择6月1日取件、6月3日归还实际租赁天数在业务上应该是2天还是3天我的处理方式是前后端统一规则租期天数 end_date - start_date按自然日计算不算归还当天。前端在选完日期后立即算出天数并展示给用户后端在下单接口里再算一遍做校验防止有人绕过前端直接调接口。关键冗余字段一定保存到订单表不要在展示时再计算避免后续状态变更时价格也跟着变。库存校验的逻辑也需要前置。下单时后端要遍历每一件商品的每种尺码检查该尺码在所选日期区间是否还有库存。这个校验不能只查总数必须结合已存在订单的日期区间做重叠判断。最朴素的做法是查询状态为“租赁中”或“已支付/已预订”的订单明细按商品和尺码过滤判断它们的订单日期区间是否与当前新订单重叠。数据量在毕设场景下完全跑得动。4. 后端核心实现从JWT鉴权到订单流转的完整链路4.1 登录鉴权与角色权限JWT 拦截器就够了很多同学一上来就上Spring Security OAuth2配置类写了一大堆最后自己都看不懂。对于这个项目来说JWT 拦截器是性价比最高的组合。用户登录成功后后端生成一个JWT token返回给前端token里包含userId和role。前端把token存到localStorage每次请求在axios拦截器里带上Authorization头。后端写一个HandlerInterceptor拦截需要登录的路径如果请求头没有token返回401。有token则解析把userId放到请求attribute里放行。如果请求路径以 /admin/ 开头继续校验角色是否为管理员否则返回403。JWT的生成和校验直接引入jjwt依赖工具类就两个方法generateToken和parseToken。这个方案不仅代码量小而且你答辩时能把“无状态认证”“角色鉴权”这些概念讲得明明白白。有一点要注意登录接口本身和注册接口必须放进拦截器白名单否则用户还没登录就被拦截了这是新手最常见的问题之一。4.2 下单与扣库存并发安全是必问的高频考点下单是整个后端最核心的接口。我有一个建议把“创建订单”和“扣减库存”放到同一个事务方法里逻辑顺序固定为校验商品状态是否上架、是否在可租日期内。校验尺码库存按商品尺码查库存表剩余量是否足够。创建订单主表记录状态初始化为待支付。创建订单明细记录。扣减对应尺码的可用库存total_count。返回订单编号前端跳转支付页。这里必须解释一个并发场景两个用户同时下单租同一件衣服的同一个尺码和同一时间段怎么保证不超卖毕设阶段的答案是数据库层面的行锁。MyBatis-Plus里可以这样处理ClothingStock stock clothingStockMapper.selectForUpdate(clothingId, size); if (stock.getTotalCount() quantity) { throw new BizException(库存不足); }selectForUpdate对应SQL中的SELECT ... FOR UPDATE会在事务内对这条库存记录加行锁。第二个事务执行到这里时会等待第一个事务提交后才会继续查询。这个方案实现简单也能讲清楚原理。如果你还想加分可以在答辩时说“这是悲观锁方案如果并发量更高可以换成Redis分布式锁或乐观锁版本”然后展示一行基于version字段的乐观锁更新SQL即可。4.3 订单状态流转接口校验、校验、再校验前端按钮点击不能直接更新status字段。每个状态变更都应该走专门的接口接口内部做合法性校验。我实际的接口划分如下POST /api/order/create创建订单状态待支付。POST /api/order/pay/{orderNo}模拟支付状态待支付转已支付。POST /api/order/cancel/{orderNo}用户取消订单待支付状态可取已支付状态则需要管理员介入。POST /api/admin/order/confirm/{orderNo}管理员确认出借状态已支付转租赁中。POST /api/user/order/return/{orderNo}用户一键归还状态租赁中转已归还。POST /api/admin/order/complete/{orderNo}管理员确认验收状态已归还转已完成。POST /api/admin/order/reject/{orderNo}管理员取消/驳回订单。每次流转更新时service层方法第一行就是校验当前状态Order order orderMapper.selectById(orderId); OrderStatus currentStatus OrderStatus.fromCode(order.getStatus()); if (currentStatus ! OrderStatus.WAIT_PAY) { throw new BizException(当前状态不可取消); }这个模式写起来很机械但胜在逻辑非常清晰排查问题时看状态变更记录一目了然。建议在订单表加一个update_time字段每次状态变更自动更新后面做数据统计和排查纠纷都用得上。4.4 统一返回结果和全局异常处理为联调省一半时间前后端联调最容易出现的问题就是“前端不知道后端返回了什么格式、报错又该怎么解析”。我的做法是定义一个统一结果类public class ResultT { private Integer code; // 200成功其他为业务错误码 private String message; private T data; }所有Controller方法返回Result。再配合一个全局异常处理类用RestControllerAdvice捕获业务异常和系统异常统一封装成Result返回。这样前端axios的响应拦截器里只需要判断codecode为200时取data否则弹message。联调阶段基本不会出现对接扯皮。5. 前端页面实现Vue项目结构与核心交互细节5.1 路由划分与登录守卫前端路由我分成两部分用户端页面和管理员页面。用Vue Router的beforeEach守卫做拦截router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next(/login) return } if (to.meta.requiresAdmin) { const role localStorage.getItem(role) if (role ! 2) { next(/403) return } } next() })管理端的路由单独拆分用children的方式挂在一个AdminLayout组件下形成后台管理的整体骨架。管理端页面在路由懒加载代码量虽然不大但这种组织方式会让项目结构特别清爽。登录页和首页是最先写的页面。首页需要从后端拿轮播图数据商品封面前几张和推荐列表这里就直接暴露了前后端联调的第一步配置axios的baseURL建议写成环境变量Vue2放到.env.development里值为http://localhost:8080/api。5.2 商品详情页日期选择器 尺码选择的联动逻辑商品详情页是整个前端交互最复杂的部分。核心场景是用户选了开始日期、结束日期和尺码之后页面需要显示租金总价同时要给出“该尺码在当前时间段是否可租”的反馈。我的实现思路是进入详情页时加载商品详情和每个尺码的基础库存。选择日期时调用后端接口GET /api/clothing/checkStock?clothingIdxxsizexxstartDatexxendDatexx返回该时间段内的余量。如果余量为0禁用提交按钮并提示“该尺码在这些日期已被预订”。注意这个接口只是一个预检查真正的强校验在下单后端事务里。前端只是提升用户体验后端才是安全底线。这个分工思想建议在答辩时主动讲出来算一个专业度的加分项。日期选择用Element UI的el-date-picker设置typedaterange即可。长度限制要设好单次最长租期建议不超过7天否则容易导致库存被长期占用也避免有人一次性把整条时间段锁死。5.3 购物车与结算页从单品到订单的数据流购物车在前端是一个不复杂但很繁琐的部分。我建议第一版把购物车表放到后端数据库而不是用localStorage存储。理由有三一是用户可以多设备登录不丢数据二是后端管理端可以看到用户加购行为三是数据库表多一张购物车表也能增加项目工作量与展示点。购物车表字段实际上就是clothing_id、size、start_date、end_date、quantity。前端购物车页面把接口返回的每一行展示出来支持修改日期和尺码、删除、清空、勾选结算。勾选结算时把选中的购物车记录id列表传给后端后端用这些记录创建订单。结算页面展示商品清单、租金明细、押金合计。用户确认后点“提交订单”跳转到支付页面。支付页面做一个倒计时比如15分钟内有效到点未支付则订单自动取消——这里其实也可以加一个定时任务扫描但前端计时提醒已经足够应付演示。5.4 管理端页面表格、弹窗、状态操作三件套管理端不要自己做花哨布局直接套Element UI的el-container布局左侧菜单、右侧内容区。商品管理页是一张表格加新增/编辑弹窗弹窗表单包含商品名称、分类、价格、详情图片上传等字段。库存修改建议在弹窗里直接提供尺码和数量动态行编辑。订单管理页是重点。表格展示订单号、用户、商品、日期、订单状态当前状态用tag标签着色。每行根据状态显示不同的操作按钮待支付显示“取消”已支付显示“确认出借”已归还显示“确认完成”。点击按钮前用this.$confirm弹窗二次确认确认后调用对应状态流转接口刷新表格。管理端最容易被忽略的是“备注/留言”这类交互。在订单详情弹窗里加一栏备注文本管理员可以记录验收问题比如“裙摆有污渍需处理”虽然不影响主流程但业务完成度立刻上了一个档次。6. 联调、部署与排错最容易卡住人的几个地方6.1 跨域问题必现的第一个拦路虎前端在localhost:8080后端在localhost:9999或者8081必定触发跨域。解法在后端配置CorsFilter放行前端来源registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600);注意allowedOriginPatterns() 在允许携带cookie凭证时需要这样配不能用allowedOrigins()否则浏览器会报错。这个细节我印象挺深第一次接触时卡了我很久。6.2 前端history路由刷新404Nginx配置一个try_filesVue默认使用history模式路由直接打包部署后点击二级页面刷新会出现404。这是因为Nginx没有找到对应的物理文件。解决方法是配置location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }这行配置的作用是请求任何路径时如果找不到对应静态文件就回退到index.html由前端路由接管。这个坑在本地开发环境不会出现只有部署上线才遇到。6.3 图片上传后访问不到必须配置虚拟路径映射商品图片如果走文件上传到服务器本地磁盘比如/usr/local/upload前端用http://ip:端口/upload/xxx.jpg访问时会报404。原因很简单SpringBoot默认只处理classpath和映射路径外部磁盘路径不在它管辖范围内。解决办法是配置一个资源映射器registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath /);这样/upload/开头的请求就会被映射到磁盘目录。这里顺便提醒Windows和Linux的路径写法不同上传目录最好不要写死放配置文件里读取。6.4 数据库时间格式与JSON序列化问题后端LocalDateTime返回给前端如果不做处理会变成一个数组格式前端没法直接用。两种解决方案一是在实体字段上加JsonFormat注解指定格式二是在配置类里自定义Jackson序列化规则。我推荐后者一个全局配置解决所有日期字段。另外MySQL连接串一定要加serverTimezone和useSSL配置否则连本地数据库都会报时区错误。6.5 部署步骤一套经过多次验证的流程后端部署本地Maven打包成jar放到服务器执行java -jar xxx.jar通过 --spring.profiles.activeprod 切换生产配置。如果你想做得更正规可以用systemd配置一个服务崩溃自动重启。毕设阶段能讲到systemd也算加分项。前端部署npm run build生成dist目录把dist上传到Nginx的html目录。注意打包前检查axios的baseURL是否指向服务器地址而不是localhost。一个完整的部署方案是Nginx反向代理后端接口前端静态资源由Nginx提供/api开头的请求转发到后端端口。这样前端代码里baseURL可以直接写/api后端配置跨域白名单时也不用把服务器IP写死。这个方案在答辩演示时最干净。7. 从“能跑”到“能讲”代码之外的答辩准备与优化方向7.1 演示动线设计带着老师走完整业务闭环很多同学演示的时候东点一下西点一下老师看得云里雾里。我的建议是设计一条固定演示路径注册一个新用户→登录→首页逛一逛→进详情页选日期尺码→加入购物车→结算下单→模拟支付→查看“我的订单”→切换管理员登录→在订单管理中找到这笔订单→确认出借→切回用户端点“归还”→管理员确认完成。整条链路走下来大约5分钟业务流程清晰展示完毕。这个演示动线也反过来检验了你的系统是否真正完整。如果走到哪一步卡住了那说明那个模块还没做完正好提前补上。7.2 答辩高频问题提前备好答案根据我和几位同学模拟答辩的经验老师最常问的问题集中在以下几个方面状态机是怎么防止非法流转的答案每个流转接口都有当前状态校验只能从合法前驱状态跳转。并发下单同一件衣服怎么处理答案事务行锁或进一步讲乐观锁/分布式锁方案。为什么订单明细要存冗余的商品名称和价格答案订单是历史事实不能随商品表变更而变。前端权限控制能替代后端吗答案不能前端只是体验优化后端拦截器才是安全边界。数据表之间是怎么关联的答案讲清楚外键逻辑和主从关系即可不是非要在数据库里建物理外键。这些问题没有一个超纲的但如果你没提前准备现场容易支支吾吾。把每个问题的“为什么”想清楚比背代码有意义得多。7.3 后续还能扩哪些方向给想加分的同学如果学有余力有几个方向很值得尝试。一是接一个第三方沙箱支付把模拟支付换成真实调用的流程项目的完整度立刻提一个台阶。二是引入Redis缓存热门商品列表和库存预检查接口给前端响应提速这个改造不到一天就能完成。三是用ECharts做一个管理端租借趋势折线图数据统计页面立刻好看很多。我个人的体会是这个题目的上限其实不低。你完全可以用同样的架构把它扩展成一个支持多店铺入驻的汉服租赁平台或者加入押金自动扣除逾期费用、信用评分体系这些业务规则。毕设是一次性项目但它背后的状态机设计、并发控制、前后端分离思维放到任何一条技术路线上都成立。把这些问题想透了答辩那天你就不是在“背”而是在“讲自己的系统”。
阅读完成 · 觉得有帮助?