1. 项目定位一个典型的“租赁预约”全链路业务系统“西安汉服妆造租赁系统”这个题目说白了就是围绕汉服租赁与化妆预约做一套完整的线上线下闭环业务。一听到这几个关键词我基本就能判断出这是个毕业设计类项目而且属于“业务场景非常完整、适合全栈练手”的那种选题。西安本身就是文旅热门城市汉服体验是这两年特别火的线下消费场景游客到店选服装、做妆造、约摄影师已经是标准化流程但大部分商家还在用微信聊天、电话登记、手写排班这些原始方式管理业务从用户到商家再到妆造师整个链路里全是效率痛点。这套系统要解决的就是这个问题用户在手机小程序上逛汉服、看妆造师、选时间段、下租聘单商家在后台管理服装库存和订单妆造师能看到自己的排班和预约记录。这类项目的核心价值不在于技术多么高深而在于把“租赁”和“预约”这两种典型业务形态组合到了一起。租赁这件事有很强的商品属性需要SKU管理、库存扣减、订单状态流转预约这件事有强的时间属性需要妆造师排班、时间段锁定、防止超卖。这两个业务都能映射到通用技术方案上所以它天然适合用Spring Boot 微信小程序这套主流全栈组合来落地。同时这种选题对做毕设的同学来说性价比很高不会像纯商城那样面试官看到都腻了又不会像算法模型类课题那样理论门槛太高是一个让技术能力和业务理解都能得到展示的题目。从适合人群角度看这个项目最适合三类人其一是计算机相关专业要做毕业设计或课程设计的学生尤其是想做Java后端或小程序开发的其二是想通过一个完整项目来学习Spring Boot接口开发、MyBatis数据库操作、小程序云开发或原生开发的同学其三就是确实有汉服租赁实体业务、想做数字化工具的小商家这套系统稍作改动也可以商用。接下来我就把整个项目从需求拆解、技术选型、数据库设计、核心接口实现到小程序端落地完整地拆开讲一遍重点讲清楚每一步“为什么这么做”以及实操中真实的坑和心得。2. 技术选型为什么是Spring Boot 微信小程序 MySQL2.1 后端框架Spring Boot是当下的默认答案后端选择Spring Boot其实是当下Java技术栈里最没有争议的一个决定。它不用像传统SSH框架那样写一堆XML配置内置Tomcat一个main方法就能启动配合Spring MVC做RESTful接口非常顺手。毕业设计答辩时老师一定会在意“为什么选Spring Boot”标准话术也很简单spring-boot-starter-web提供快速Web开发能力spring-boot-starter-validation处理参数校验MyBatis Plus简化单表CRUD这套组合能在最小配置成本下支撑起整个业务后台。如果你做的是带权限控制的项目再加一个Spring Security或拦截器实现Token鉴权就能覆盖核心需求。Spring Boot版本建议选2.7.x不太建议上手就追最新的3.x因为3.x基于Jakarta命名空间对某些依赖兼容性有影响尤其是一些毕业设计教程和开源代码都是基于2.x封装的用3.x会遇到很多“别人没踩过”的坑。热词里那个“springboot版本太高”其实就是这个意思版本太高导致代码生成器、插件不兼容是新手最常见的翻车现场。2.2 小程序端原生开发是毕设的最稳选择小程序端这一块个人强烈建议用微信原生小程序开发而不是一上来就套uni-app或Taro这类跨端框架。原因很直接毕设项目要的是快速出成果、稳定运行和答辩时能讲清楚原理。原生小程序使用WXML、WXSS、JavaScript语法本身不复杂不需要额外学习跨端编译的抽象概念微信开发者工具自带调试器、真机预览网络请求在详情面板里一目了然这对联调阶段极其友好。跨端框架的优势是“一套代码多端复用”但这个项目的目标终端只有微信小程序一个没必要杀鸡用牛刀。2.3 项目目录结构与分层设计后端代码按照经典的Controller-Service-Mapper三层结构组织这是最“标准答案”式也最好讲解的设计src/main/java/com/example/hanfu/ ├── controller # 接收前端请求参数校验返回Result ├── service # 业务逻辑事务管理订单状态流转 ├── mapper # MyBatis Plus的Mapper接口 ├── entity # 数据库实体类 ├── config # 拦截器、跨域、全局配置 ├── common # 统一返回结果、异常处理、工具类 └── HanfuApplication.java这个结构的核心思想是各层单向依赖Controller只负责接参数和返回结果不写业务Service负责业务规则比如下单扣库存、时间冲突检查Mapper只做数据库交互。好处是答辩时被问到“订单流程在哪改”时你能清楚定位到service层被问到“参数校验怎么做的”时你直接指controller层的注解逻辑非常清晰。小程序端目录也按业务模块拆分pages下分首页、分类、详情、购物车、订单、预约、个人中心等页面公共方法集中在utils目录。3. 数据库设计一张订单表撑起两条业务线数据库设计是这类项目的重头戏面试和答辩的重点都在这。很多同学急着写代码表结构拍脑袋就定后面改需求改到崩溃。这个项目的核心是“租赁预约”双业务线但本质上可以收敛到一套统一模型用户、商品汉服、妆造师、排班时间、订单、订单明细。3.1 核心表清单与职责划分按实际项目经验这套系统至少要设计以下数据表它们的职责可以对照表格来看表名核心作用关键字段user小程序用户信息openid, nickname, phone, avataradmin管理后台账号username, password, roleclothing_category汉服分类name, sort, cover_urlclothing汉服商品name, category_id, size, stock, rent_price, deposit, statusclothing_image商品多图clothing_id, image_url, sortbeautician妆造师信息name, avatar, skill_tags, introductionbeautician_schedule妆造师排班beautician_id, work_date, time_slot, max_countappointment妆造预约单user_id, beautician_id, schedule_id, appointment_time, statuscart_item购物车user_id, clothing_id, quantity, selectedorder订单主表order_no, user_id, total_amount, status, type, create_timeorder_item订单明细order_id, clothing_id, item_type, price, quantity, rent_start, rent_end这里最需要理解的是订单为什么要拆主表和明细表。因为一个订单可能同时包含“租赁两套汉服”和“预约一个妆造服务”租聘是按天计价、押金可退妆造是按时段计价、服务完即结束这两种商品类型属性差异太大放在一张表里会非常臃肿。拆成order和order_item两张表后order表只存订单整体属性总金额、状态、类型order_item表按行记录每一件商品或服务。后面统计营收、商家对账都会方便很多这也是业务项目的基本功。3.2 权限设计用户、管理员与妆造师三种角色权限设计上这套系统实际存在三类角色但小程序端只面向普通用户管理端面向管理员妆造师是管理后台的“被管理对象”。用户表直接存openid用户首次进入小程序时自动创建账号只有用户主动填了手机号或昵称时再更新。这里有一个很关键的小细节openid是微信小程序里标识用户的唯一凭证不要设计成用户自己输入账号密码登录小程序里没有密码输入场景一律走微信授权登录后端通过code换取openid并签发自定义Token。管理员表单独建不走小程序通道直接在管理后台登录密码用BCrypt加密存储答辩时可以指出来这是符合安全规范的。3.3 订单状态流转设计订单状态是这个系统里最能体现“业务严谨性”的地方。我见过很多毕设项目订单状态就是随便一个字符串全凭前端乱传这是最致命的错误。正确做法是定义一个状态机用数字或固定枚举值表示后端Service层严格控制状态跳转租赁订单待支付(0) - 已支付/待取货(1) - 租赁中(2) - 已归还/待退押金(3) - 已完成(4) - 已取消(5) - 已退款(6) 预约订单待确认(0) - 已确认(1) - 服务完成(2) - 已取消(3)状态机的好处是防止非法操作比如一个已取消的订单不能再被改成已完成。后端接口里要加状态校验逻辑例如用户只能取消“待支付”状态的订单管理员只能对“已支付/待取货”状态的订单执行发货操作。代码层面用if判断状态分支即可没必要上状态机框架比如Spring StateMachine毕设讲清楚自动流转的规则就够了。4. 后端核心功能实现从登录到下单再到预约4.1 微信登录与Token鉴权微信登录是这套系统第一个接口也是小程序与后端联调时最容易卡住的一环。流程不复杂但非常严格小程序端使用wx.login()拿到临时code把这个code发给后端后端拿着code调用微信接口https://api.weixin.qq.com/sns/jscode2session换取openid和session_key后端查数据库如果这个openid不存在就自动注册一个新用户最后后端自己生成一串TokenJWT或UUID都行返回给小程序端。后续所有请求都带上这个Token后端通过拦截器解析Token识别用户身份。这里必须提醒的是jscode2session的调用必须放在后端完成绝不能在小程序端直接调。因为调用时需要appid和secretsecret是开发者私密凭据一旦被暴露在客户端任何人都能伪装成你的服务器获取用户数据。这一点在答辩时被老师问到的概率非常高答不上来会很减分。下面这个统一返回结果类是后端所有接口的“门面”风格保持一致前端解析也方便Data public class ResultT { private Integer code; // 200成功500失败401未登录 private String message; private T data; public static T ResultT ok(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT fail(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }登录接口的大致逻辑是Controller接收code参数 - Service调用微信API - 解析openid- 查询或创建用户 - 用UUID生成token存到redis或者直接返回 - 返回用户信息。如果没有引入Redis把token直接存到user表的一个字段里其实也能用But毕设里建议加一个简单的JWT工具类用io.jsonwebtoken生成带过期时间的token这样更专业。4.2 汉服租赁下单链路购物车与库存扣减租赁下单是最核心的业务逻辑它比单纯电商多了一件事库存不是永久扣减而是按租赁时间段去计算占用。一套汉服在10月1日被订走了10月5日归还那10月2日这套服装可能还是可预订的。但毕设项目为了简洁通常会把库存简化为一个固定数值提交订单时判断stock是否大于0下单成功就把stock减一归还时再加一。如果你想把“按日期库存”做得更细就需要引入clothing_sku和inventory两张表按日期维度去记录可售数量逻辑会复杂不少建议有时间再优化。购物车转订单的流程按业务先后顺序可以拆成四步前端把购物车选中的商品列表传给后端后端校验每件商品当前状态是否可租、库存是否充足创建order主表和order_item明细表计算总价并生成订单号最后如果库存不足提示用户哪件商品没货了并回滚整个事务。这几步必须在同一个数据库事务里否则容易出现“订单创建失败但库存被扣了”的怪问题。Service方法上加Transactional注解即可这是最基础也是最不能忘的操作。订单号的生成也是一门小学问。SimpleDateFormat拼接毫秒时间戳是新手做法高并发下会重复更规范的方式是用“时间戳随机数用户ID尾号”生成唯一业务订单号。这里提供一个适合毕设的生成方法public static String generateOrderNo() { String timestamp new SimpleDateFormat(yyyyMMddHHmmss).format(new Date()); int random (int) (Math.random() * 9000) 1000; return timestamp random; }4.3 化妆预约时段管理与冲突检测化妆预约是这套系统的另一条业务线难点在排班与冲突检测。妆造师每天有多个可选时段比如上午9点到12点、下午1点到5点每个时段可接待的顾客数量有限通常1到2人。后端需要维护beautician_schedule表每个时段生成一条排班记录记录最大可预约人数和已预约人数。用户选择某个妆造师、某个日期、某个时段后前端校验该时段是否在可预约列表里传给后端时后端要再次查询该时段的current_count是否小于max_count如果小于就原子性执行UPDATE beautician_schedule SET current_count current_count 1 WHERE id ? AND current_count max_count影响行数为1才说明抢到了否则提示“该时段已约满”。注意这个更新语句不能拆成select再update否则两个人同时预约同一时段一个事务看不到另一个事务的未提交修改就会超卖。API层面可以设计成/api/appointment/book参数包含beauticianId、scheduleId、appointmentTime、remark。双人并发预约同一个妆造师防超卖这个问题建议在博文里单独提一下不能先查库存再判断必须用UPDATE的条件原子判断这是分布式锁之外最简单可靠的并发控制方案面试官会眼前一亮。4.4 管理员端数据看板与订单管理管理后台技术栈可选Vue或Bootstrap模板但更省事的方案是直接写Thymeleaf模板页面或者用现成的AdminLTE这类开源模板改。管理员端至少需要这些功能模块仪表盘数据统计、汉服商品管理上架/下架/库存调整、订单列表与发货操作、妆造师排班管理、用户管理。数据统计方面可以做一个简单的首页看板用SELECT COUNT(*)查询订单总量、用SUM计算营收、按日期GROUP BY统计近7天订单趋势这些SQL对毕设来说完全够用不需要引入复杂的数据报表框架。5. 小程序端实现要点页面、请求与体验细节5.1 页面结构与核心交互设计小程序端是一个面向C端消费者的应用页面结构需要贴合真实使用习惯。建议按以下tab布局设计首页展示轮播图、热门汉服、妆造师推荐分类页按“唐制、宋制、明制、晋制”等风格筛选汉服商品详情页展示多图轮播、价格、库存、规格选择身高/尺寸和“立即预约妆造”入口购物车页管理已选商品订单页切换“租赁订单/预约订单”两个tab区分展示不同业务线我的页面包含个人信息、优惠券可选、联系客服、关于系统。商品详情页有一个容易忽视的开发点服装尺码和状态按钮的联动。用户选了尺码后才能看到该尺码是否可租这里不能只靠前端静态判断需要调用接口查询对应SKU的库存与状态。页面加载时先拿到商品基础信息和图片列表用户点击尺码规格时再发请求查询实时库存返回结果直接驱动UI状态整体体验会更专业。5.2 Request请求封装与Token自动注入小程序的网络请求不能到处直接写wx.request否则后期维护会很痛苦。封装一个公共请求工具统一管理baseUrl、请求头、超时时间和错误提示是任何正经项目的第一步。以下是我在类似项目里一直在用的简化版封装// utils/request.js const BASE_URL http://192.168.1.100:8080/api; function request(options) { return new Promise((resolve, reject) { const token wx.getStorageSync(token); wx.request({ url: BASE_URL options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, Authorization: token ? token : }, success: (res) { if (res.statusCode 401) { wx.removeStorageSync(token); wx.navigateTo({ url: /pages/login/login }); reject(new Error(登录已过期)); return; } if (res.data.code 200) { resolve(res.data.data); } else { wx.showToast({ title: res.data.message, icon: none }); reject(new Error(res.data.message)); } }, fail: (err) { wx.showToast({ title: 网络异常, icon: none }); reject(err); } }); }); } module.exports { get: (url, data) request({ url, data, method: GET }), post: (url, data) request({ url, data, method: POST }) };baseUrl的配置是个反复出现的坑本地开发时用局域网IP可以但微信开发者工具里真机预览时手机会访问不到电脑的localhost所以要么用电脑的实际局域网IP要么把后端部署到云服务器。发布上线前还必须在小程序后台配置request合法域名必须为HTTPS否则上线后所有请求都会被拦截。实际上在开发阶段开发者工具是可以勾选“不校验合法域名”的但上线预览版本时这个选项就失效了。5.3 分页列表与下拉刷新首页的汉服列表不能一次性查完全部数据标准做法是分页加载。后端接口统一接收两个参数page和size返回{ records, total, current, size }结构小程序的onReachBottom触发后自动加载下一页。需要注意的细节是列表数据必须用追加方式更新而不是整体覆盖。正确的思路是Page({ data: { list: [], page: 1, size: 10, hasMore: true }, async loadList(reset) { if (!reset !this.data.hasMore) return; if (reset) { this.setData({ page: 1, hasMore: true }); } const data await api.get(/clothing/page, { page: this.data.page, size: this.data.size }); const newList reset ? data.records : [...this.data.list, ...data.records]; this.setData({ list: newList, hasMore: this.data.page * this.data.size data.total, page: this.data.page 1 }); }, onReachBottom() { this.loadList(false); }, onPullDownRefresh() { this.loadList(true).then(() wx.stopPullDownRefresh()); } });reset参数用来区分是刷新还是加载更多避免切换tab时旧数据残留。还有一个小细节wx.stopPullDownRefresh需要在数据刷新完成后手动调用否则顶部加载动画不会消失这个在文档里有写但很多新手总会忘。5.4 真机调试与体验细节小程序联调阶段我建议用“微信开发者工具 真机预览”双模式并行。工具里可以模拟大部分交互但相机拍照、定位、手机号授权这类能力必须真机才能验证。真机预览时让手机和电脑连同一个WiFi后端启动时不绑定localhost直接用Spring Boot默认端口监听所有网卡然后把电脑局域网IP填到baseUrl即可。这里有个隐形坑Windows防火墙默认会拦截外网访问8080端口第一次真机调试网络失败时应优先检查防火墙入站规则而不是怀疑后端代码写错了。顶部导航栏的自适应也是中国程序员特有的痛点。小程序顶部导航栏在不同机型上的高度不一致iPhone X以上有刘海Android厂商各种奇葩屏幕比例。最稳的方案是使用navigationStyle: custom自定义导航栏再通过wx.getMenuButtonBoundingClientRect()获取右上角胶囊按钮位置动态计算导航栏高度。不过毕设完全可以退一步直接用默认导航栏省心省力只要注意页面标题不要被刘海挡住就行。6. 部署运行与踩坑实录6.1 本地启动前后端的完整步骤拿到这套源码之后正确的启动顺序是先启动后端再启动前端顺序反了容易产生“小程序请求失败”的无效排查。后端启动前要确认三件事JDK版本建议1.8或11、Maven是否配置好国内镜像、MySQL是否创建好对应数据库并执行了SQL脚本。启动命令可以IDE里右键运行main方法也可以在项目根目录下执行mvn clean package -DskipTests java -jar target/hanfu-system-0.0.1-SNAPSHOT.jarSpring Boot应用起来之后先确认两个地址是否可访问后端接口地址如http://localhost:8080/api/clothing/list和Swagger接口文档地址如果项目里集成了springdoc-openapi通常是http://localhost:8080/swagger-ui.html。接口文档在线可看答辩演示时是一个很加分的亮点。后端没问题后用微信开发者工具导入小程序项目目录改成自己的AppID在“详情-本地设置”勾选“不校验合法域名”然后编译运行。首次运行时如果页面白屏最可能是Token为空导致接口全部401先清缓存重新登录看看。6.2 常见问题速查表下面这些坑是我在带多个项目过程中反复遇到的整理成速查表碰到可以直接对号入座。这些问题95%都是配置或环境问题不是代码逻辑问题但排查起来特别消耗时间。现象根因解决方式后端启动失败Port 8080 was already in use端口被占用杀掉占用进程或改application.yml端口数据库连接报Communications link failureMySQL没启动或URL配错jdbc:mysql://localhost:3306/hanfu?useSSLfalse确认数据库名一致小程序请求报url not in domain list没有配置合法域名开发者工具勾选“不校验合法域名”真机预览无法请求localhost手机关机访问不到电脑本地地址改成电脑局域网IP关闭Windows防火墙或添加入站规则登录后返回404路由表配置错误常见是Controller漏写RequestMapping检查类头部路径与方法路径拼接下单库存不变Service事务没生效或者漏调clothingMapper.decreaseStock()检查Transactional是否在public方法上检查是否真正执行了扣减语句小程序页面下拉刷新没反应app.json里没开enablePullDownRefresh页面json加上enablePullDownRefresh: true图片上传只显示本地路径后端没有存储到服务器静态目录上传后返回外网可访问URL上传目录配到file.upload-dir时间格式显示为2025-01-01T12:00:00后端没有对Jackson的LocalDateTime做格式化在application.yml配置spring.jackson.date-format或字段加JsonFormat订单状态一改就错前端直接传status参数后端按状态机流程设置状态前端只能传动作类型比如cancel、pay时间格式这个坑特别值得展开一下。Spring Boot 2.x默认对LocalDateTime序列化会带上字母T前端要显示像“2025-06-01 14:30”这种格式就得统一格式化。最简单的做法是在配置文件里加一段spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8如果项目里有一些字段还是返回了数组或别的非预期格式就在实体字段上加JsonFormat(pattern yyyy-MM-dd HH:mm:ss)局部兜底。层次不齐的日期格式会在答辩演示时显得很业余值得提前统一。7. 从毕设到实战这套系统还能怎么延伸写完核心流程以后我再从带项目的经验出发聊聊这套系统身上常见的扩展空间。很多同学毕设只要“能跑就行”但如果你想在答辩里多拿几分或者未来想把它写进简历作为项目经验下面这几个点可以帮你把项目从“作业级”提升到“作品级”。第一个是支付闭环。当前系统大概率是模拟支付前端支付后将订单标记为已支付。如果时间允许可以接入微信支付V3至少把统一下单和回调验签的流程走通这在简历上是一个实打实的亮点。第二个是优惠与营销体系。租赁行业非常讲究“押金退还”和“套餐组合”可以增加优惠券表、满减活动配置、推荐有礼等模块。组合套餐其实很适合汉服场景比如“汉服租赁妆造跟拍”一键打包下单这个设计不但业务上合理演示效果也好。第三个是消息通知。微信小程序可以通过订阅消息在“预约成功”“订单待归还”“妆造服务即将开始”等节点给用户推送提醒。实现上只需要在小程序端申请订阅消息模板后端在状态流转时调用subscribeMessage.send接口即可。对用户体验的提升是肉眼可见的。第四个是数据可视化。管理端的数据看板目前可能只是几个数字可以引入ECharts画折线图和饼图展示每日订单量趋势、各分类服装的租赁占比、妆造师热度排名。这是很多答辩组老师比较喜欢的部分因为能看到“管理系统”的感觉。这些扩展不一定都要做完选一两个和你简历方向匹配的去实现就足够形成差异化。我在实际带项目时通常建议学生优先做微信支付接入因为它既是业务闭环的关键环节也是一段能拿得出手的“硬核”经历。最后分享几个我在整理这类全栈项目时反复体会到的小经验。第一所有接口参数必须做后端校验不要相信小程序端传来的任何数据NotNull、Validated这些注解两分钟就能加完但能避免一堆莫名其妙的空指针。第二联调时遇到“前端觉得后端有问题、后端觉得前端有问题”的僵局第一步永远是打开开发者工具的Network面板和后端控制台日志对照看大多数问题都能在请求体和响应体里直接找到答案。第三写代码之前先把表结构设计清楚一张表改字段的成本在开发后期会指数级放大磨刀不误砍柴工这句老话在工作里依然是真理。这套“西安汉服妆造租赁系统”看起来只是一个毕设选题但它的业务模型覆盖了商品管理、订单流转、预约排期、权限控制等企业级系统的高频核心点。无论你是为了完成学业还是想借这个项目摸清Spring Boot和小程序的全栈链路把它彻底理解消化掉收获的肯定不只是那一行行能跑的代码。