1. 从西安汉服热到系统需求:这个项目到底要解决什么问题
前两年在西安出差,晚上去大唐不夜城转了一圈,满街都是穿汉服的年轻姑娘和小伙子,手里提着化妆包、拎着租来的衣服。当时我就觉得,这个场景里藏着很大的生意,但门店的运营方式还特别原始——纸质登记、电话预约、口头确认,旺季的时候乱成一锅粥。
这个项目的出发点就是西安汉服妆造租赁和化妆预约这个细分场景。表面上做的是"一家汉服店的线上化",但真正要解决的问题比想象中复杂:汉服的尺码规格不统一,妆造服务有很强的时段属性,租赁有"租几天、什么时候还"的时间维度,还牵扯到押金、超时费、损坏赔付这些钱的问题。我在梳理需求的时候,把整个业务链路拆成了四块:用户逛店选衣服、线上预约妆造时间、到店试穿取件、归还结算。
系统分两端:用户端是微信小程序,商家端是SpringBoot后台。用户在小程序里看汉服款式、查妆造师档期、下单预约、在线支付押金和租金;商家在后台管理库存、维护套餐、处理订单、设置门店营业时间。这个分工很明确,小程序做触达和转化,后台做管理和运营,中间通过REST接口对接。
这个项目适合谁?一类是想把汉服租赁从线下搬到线上的实体店主,另一类是正在做Java毕设或者找工作练手项目的同学。从技术角度看,它覆盖了微信小程序登录、支付对接、订单状态机、库存扣减、预约排期冲突检测这些常见但不容易做好的点,麻雀虽小五脏俱全。从业务角度看,它把旅游城市的特色服务业态和移动互联网技术结合在了一起,算是文旅数字化里比较典型的一个样本。
我在设计功能清单的时候,没有一上来就堆功能,而是先列了业务上的硬约束:营业时间内才能约、同一时段一个妆造师只能接一单、衣服被租出去了就不能再被下单、订单取消要区分用户主动取消和商家强制取消。这些业务规则直接决定了后面表结构怎么设计、接口怎么防并发。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型背后的取舍:SpringBoot + 微信小程序为什么是这套组合
技术选型这件事,很多刚入门的人容易犯一个错误——什么火选什么。我在这个项目里坚持用SpringBoot + 微信小程序 + MySQL的组合,不是因为这套技术栈最新,而是因为它最适合这个业务场景,也最适合大多数人的维护能力。
2.1 后端为什么选SpringBoot而不选其他框架
SpringBoot现在几乎成了Java Web开发的默认起点。它最大的优势不是某个单一功能,而是整条链路的完备性:SpringMVC处理REST接口、MyBatis-Plus做数据持久化、Redis扛缓存和分布式锁、Maven统一管理依赖、内置Tomcat直接打包运行。项目里我用的就是这套标准组合,没有引入太重的东西。
对比一下别的方案:用Python的Flask或者Django也能做,语言更轻快,但电商交易这块涉及订单、支付、库存的时候,Java生态里成熟的解决方案更多,而且面试和简历上的认可度也更高。用Node.js做后端、小程序端也是JavaScript,语言统一,但业务复杂到一定程度后,类型不安全的缺点会被放大。SpringBoot的"重"在项目初期是劣势,但在这个有支付、有状态流转、有并发扣库存的系统里,"重"反而成了稳的意思——框架已经把事务管理、参数校验、异常处理这些基础设施都准备好了。
2.2 小程序端为什么比H5或者App更合适
微信小程序在这个场景里的优势是天然的。汉服租赁和妆造预约的决策链路是"看到-感兴趣-立刻预约",用户在大唐不夜城逛街的时候,扫码进小程序,看款式、约时间、付定金,整个过程在两三分钟内完成。小程序免安装、免注册(直接微信授权登录)、微信支付天然打通,这三个特点直接降低了转化门槛。
对比H5,小程序在支付能力、消息触达(订阅消息提醒预约成功、临近提醒)上体验好得多;对比原生App,开发和审核成本完全不是一个量级。而且围绕小红书的种草-小程序转化,现在已经形成了旅游城市消费的标准路径。这套系统的用户端页面我用了微信原生框架加Vant Weapp组件库,没有上Uni-app这一类跨端框架,原因是项目业务不复杂,原生开发的调试体验和性能都更可控,也方便讲解的时候逐行说清楚。
2.3 数据库设计:从业务关系推导表结构
数据库是这个项目最底层的部分,也是我花时间比较多的地方。整体设计了9张核心表:
| 表名 | 用途 | 关键字段 |
|---|---|---|
| user | 用户信息 | openid、昵称、头像、手机号、积分 |
| hanfu | 汉服款式 | 名称、分类、尺码集合、租金、押金、封面图 |
| hanfu_inventory | 汉服库存明细 | 款式ID、尺码、状态(在店/已租/待还)、归还时间 |
| stylist | 妆造师 | 姓名、头像、简介、服务时段、是否在职 |
| appointment | 妆造预约 | 用户ID、妆造师ID、预约日期、开始时间、结束时间、状态 |
| rental_order | 租赁订单 | 订单号、用户ID、衣物明细、总租金、押金、租期、状态 |
| payment | 支付流水 | 订单号、支付方式、支付金额、第三方单号 |
| order_item | 订单明细 | 订单ID、汉服ID、尺码、数量、单价 |
| store_config | 门店配置 | 营业时间、联系电话、公告、地址 |
关键的设计思路是:汉服和库存分开两张表。汉服表管"有哪些款式"(比如"钟灵记·嫦娥"这个SKU),库存表管"具体哪一件衣服在哪"(比如3号库房里的M码嫦娥)。租赁的时候锁定的是具体的某一件衣服,而不是笼统的SKU数量,这样归还、清洗、上架的状态流转才清晰。
用户和微信openid直接绑定,一个openid对应一个用户,这是小程序登录逻辑的基础。租期设计成"开始日期-结束日期"的区间模式,加上"逾期未还"的自动状态计算,避免用死板的"租3天"这种字段,因为用户实际归还时间往往是灵活的。
3. 核心业务模块拆解:租赁、妆造预约、订单这三块怎么落地
这个系统的业务核心是三块:汉服租赁、妆造预约、订单支付。这三块互相穿插但逻辑独立,分开讲更容易理解。
3.1 汉服租赁模块:库存状态机与多尺码匹配
汉服租赁和普通商品购买最大的区别在于:普通商品卖出去就结束,租赁要考虑"借出-归还-清洗-上架"的生命周期。我设计了一个四状态库存模型:
- 在店(IN_STORE):衣服空闲,可以被下单
- 已租(RENTED):被订单占用,未归还
- 清洗中(CLEANING):归还后进入清洗流程,不可下单
- 下架(OFF_SHELF):损坏、丢失或换季,不可下单
用户下单的时候,系统按照"款式+尺码+租期"去匹配库存表里的在店衣服,匹配到就锁定,锁定后状态改成"已租(待取)"。归还的时候从"已租"改成"清洗中",清洗完成标记为"在店",这条链路非常直接。
多尺码匹配是这里面比较烦的细节。汉服的尺码有两套体系:一套是常规的S/M/L/XL,另一套是汉服特有的"三米摆""六米摆""齐胸襦裙小码"这种按放量算的规格。我在hanfu表里用一个JSON字段存了尺码组(比如["S","M","L"]),在hanfu_inventory表里每条库存记录存具体的尺码值。前端展示的时候按款式聚合展示"还剩几件、各什么码",用户选码之后再去锁定库存。
这里有一个非常关键的细节:下单时要同时检查库存表的衣服状态和订单表的租期区间。同一件衣服,A用户还了但B用户30号就要租,中间还隔着清洗时间,那1号到15号可以租、20号到28号可以租,16号到19号就不行,因为清洗需要时间。所以我不是简单查"这衣服在不在店",而是查"这件衣服在用户要的租期区间内是否被其他订单占用",这个查询用一条带时间区间重叠判断的SQL就能实现:
sql复制SELECT COUNT(*) FROM rental_order_item
WHERE hanfu_id = #{hanfuId}
AND status IN ('PAID', 'RENTED', 'PENDING_PICKUP')
AND rent_start_date <= #{rentEnd}
AND rent_end_date >= #{rentStart}
3.2 妆造预约模块:排期冲突检测与时段划分
妆造预约本质上是一个日历调度问题。一个妆造师一天能接的客户数是有上限的,化妆+做发型平均要40到60分钟,上午下午可用的时段不一样。我按30分钟为一个最小时间片,把营业时间(比如10:00-20:00)切分成时段,每个时段在每个妆造师下面是一个可预约的slot。
用户在微信小程序里选日期、选妆造师、选时段,提交后后端做两道校验:第一道,校验营业时间,超过门店关门时间直接拒绝;第二道,校验该妆造师这个时段是否已经被占用。占用判断用的是"开始时间小于已有预约结束时间,且结束时间大于已有预约开始时间"这种标准的区间重叠判断。
这里我踩过一个并发相关的坑,下面第五节会细讲。提前说一句话:接口层的校验在并发场景下是不可靠的,必须配合数据库的唯一约束或者分布式锁,不然两个用户同时提交同一个时段,数据库会插进去两条冲突记录。我最终用的是Redis分布式锁,锁的key就是"stylist:{stylistId}:{date}:{timeSlot}",加锁成功才允许插入预约,预约完成释放锁,这个方案实测很稳。
妆造预约和租赁订单是解耦的。用户可以只租衣服自己画,也可以只约妆造穿自己的衣服,也可以两件事一起。我用了两个独立的订单类型:租赁单(RENT)和妆造单(MAKEUP),对应的表和流程不一样,但都走同一个支付网关。这种"解耦但统一"的设计,让后续加新品类(比如摄影约拍)会很方便。
3.3 订单状态流转:从下单到结算的完整闭环
订单状态是这个系统里最容易写乱的部分。我给自己立了个规矩:所有订单状态用枚举管理,状态流转只通过一个OrderStateMachine服务来做,不允许在业务代码里到处setStatus。
租赁订单和妆造订单的状态流不一样,我分开列一下:
租赁订单状态流:
- PENDING_PAYMENT(待支付):用户提交订单,锁定库存,15分钟后未支付自动释放
- PAID(已支付):支付成功,等待用户到店取衣
- PENDING_PICKUP(待取件):到店核实身份,店员确认取件
- RENTED(租赁中):衣服在用户手上
- RETURNED(已归还):用户归还,进入"待结算/待退款"环节
- SETTLED(已结算):扣除租金、退还押金,订单闭环
- CANCELED(已取消):用户取消或超时取消
- OVERDUE(已逾期):超过应还日期未归还,系统自动标记
妆造订单状态流相对简单:
- PENDING_PAYMENT(待支付)
- PAID(已支付,预约成功)
- COMPLETED(服务完成)
- CANCELED(已取消,需要超过预约时间2小时前)
每个状态节点都对应一个操作:待支付对应"调用微信支付",取件对应"店员扫码确认",归还对应"检查衣物并登记损坏项"。这个状态下后来看,最值钱的设计就是"CANCELED之后库存怎么回来"——取消订单必须回滚库存,把锁定的衣服恢复到在店状态,同时释放妆造师的时段。这个回滚逻辑如果散落在各种controller里,迟早会漏。
4. 微信登录与小程序端几个关键实现
小程序端不是这个项目的核心难点,但有几个点是绕不开的,微信登录、预约日历、状态联动,每一个都值得单独说。
4.1 微信登录:code5分钟过期与openid绑定
小程序的登录流程看起来简单,但实现时有一个很多人容易搞错的点。我从流程上讲一下:
- wx.login()获取临时code(有效期5分钟,且只能用一次)
- 小程序把code传给后端
- 后端拿code + appid + appsecret去微信接口换openid和session_key
- 后端查user表里有没有这个openid,没有就新建用户
- 后端生成自定义token(我用的是JWT)返回给小程序,后续所有请求带这个token
网上很多教程把第一步和第三步混在一起,或者是前端直接拿code换openid但换完没有自定义登录态,结果每次启动都要重新登录。正确做法是:换openid这一步必须在后端做,因为appsecret不能暴露在小程序代码里,否则任何人都能通过反编译拿到你的密钥,后果很严重。
登录态过期的问题我再展开说一句,小程序端的storage里存了token,但这个token自己会过期(我设置的7天)。为了不让用户频繁重新登录,我做了静默续期:每次请求后端时后端检查JWT过期时间,如果快过期了(还有不到1天),就重新签发一个token放在响应头里,小程序端拦截器检测到新token就覆盖存储。这个体验细节很多人不做,但直接影响留存率。
4.2 预约日历与时段选择的交互设计
小程序端预约页面核心是一个日历选择器加时段列表。日历我一开始想用第三方组件,后来发现外卖点餐式的预约(选完日期马上显示当天还有哪些时段可选)更适合这个场景,就用原生scroll-view配合自绘实现了一个月历组件。周一到周日横向排,点击日期,右边的时段列表刷新,已约满的时段显示"已满"并置灰。
时段列表的数据来自后端接口:传入日期和妆造师ID,返回该妆造师当天的全部时段及占用状态。这个接口有一个性能优化小技巧:不要前端按日期一次只查一天,而是加载当月全部时段的占用情况缓存下来,用户每切换一天就看本地map缓存,秒开。数据变化了再去刷新缓存,这个优化让页面从"切一天转圈1秒"变成"切一天零延迟"。
4.3 库存余量与订单的实时联动
汉服详情的"剩几件"是用户最关心的信息之一。这个数字如果等到下单时才校验,用户选好了尺码却发现没衣服,体验就差了。我做了两级库存实时联动:
- 列表页轻量展示:显示"S码还剩3件",这个数据来自Redis缓存,每30秒同步一次数据库
- 校验页精确锁定:用户下单提交时,走数据库级别的行锁校验,确保万无一失
两级设计避免了一个经典性能问题:直接查数据库判断库存,高峰期一天几万次查询会把MySQL拖垮。Redis的常规键值结构足够用了,key设计成"hanfu:qty:{hanfuId}:{size}",value就是剩余件数,扣减用INCRBY原子操作。数据库库存扣减则在订单事务里用带行锁的查询,两条路各管各的,数据最终一致性通过定时任务对账保证。
5. 开发过程中的踩坑记录与根因排查
这个项目我前后开发了大概四周,踩过的坑不少,挑三个最有代表性的出来晒一晒。这三个坑的特点都是"现象怪、根因深",不排查到数据库层面根本发现不了。
5.1 小程序登录态凭空失效:wx.login的code被提前消费
上线后的第一个Bug:部分用户登录后没过几天,操作就报"登录态失效,请重新登录"。看日志发现,这些用户的openid确实查到了,但后续请求带过来的token解析失败。
排查过程是这样的:一开始怀疑是JWT密钥的问题,但同一个密钥其他用户没问题。后来打开用户操作链路,发现这些用户都有一个共同点——都是从微信"我的-设置-隐私"里清过缓存。因为我的旧代码里把"登录态"设计成了"每次进入小程序时,如果storage里没有token就重新wx.login"。用户清缓存后token没了,小程序重新走wx.login拿到新code,后端正常换openid、正常签token。但问题是——旧token还在后端Redis白名单里,新token签发后旧token还没被清除,而小程序端因为新token覆盖了旧token,看起来应该没问题才对。
最后发现问题出在JWT签发时我加了一个"jti"字段(token唯一标识),Redis白名单的key是jti,过期时间是7天。新token签发时,我把旧token的jti删了,但是!删除用的key拼错了,写成了"token:blacklist:{oldJti}",而存的时候存的是"token:jti:{jti}"。删除时查不到key,旧token一直有效,但前端早就换了新token,新token还没进白名单。后端校验新token时发现jti不在白名单,就报了失效。
这个Bug的教训很粗暴:key的一致性是缓存类Bug的头号来源,拼写、前缀、命名风格任何一个不一致都会引发"数据明明存了却查不到"的诡异问题。我把项目里所有的Redis key统一收敛到一个KeyConstants类里定义,后续再没发生过类似问题。
5.2 妆造预约时段冲突:接口校验挡不住并发
开发预约功能的时候,我在接口层做了区间重叠校验,本地测试单线程怎么点怎么没事。部署到测试环境,让两个同事同时用不同的手机抢同一个妆造师的同一个时段,竟然都成功了。数据库里出现了两条重合的预约记录。
我排查时说一句实话:当时第一反应是怀疑测试环境的事务隔离级别,因为MySQL默认的REPEATABLE READ下,"先查后插"在高并发下有一个经典的坑——两个事务同时查,都查不到对方未提交的记录,然后都插入成功。我当时的校验SQL是:
sql复制SELECT COUNT(*) FROM appointment
WHERE stylist_id = #{stylistId}
AND date = #{date}
AND start_time < #{endTime}
AND end_time > #{startTime}
Count返回0,然后两个事务都执行INSERT,都成功了。REPEATABLE READ下,间隙锁的机制在非唯一索引的区间判断上并不能完全阻止插入,尤其是条件里面带<和>这种范围判断的时候。
我的修复方案分两层:
- 数据库层加唯一约束,用
UNIQUE KEY (stylist_id, date, start_time),从物理上保证同一时段只能有一条记录 - 应用层用Redis分布式锁做前置判断,加锁成功才允许走后续逻辑
两层都上了之后,并发问题彻底消失。我的建议是:凡是涉及"判断后插入"这类操作,第一个方案永远是数据库唯一约束,分布式锁只是锦上添花,单独依赖应用层判断在极端情况下总会有漏网之鱼。
5.3 图片上传与小程序临时文件路径
小程序里上传图片有两种方式:一是用wx.chooseMedia拿到临时文件路径后直接调用wx.uploadFile上传到服务器;二是用wx.uploadFile配合formData携带业务参数一起提交。我一开始走了弯路,用wx.chooseMedia后直接把临时路径存到了后端数据库,导致第二天刷新页面,所有图片全裂了。临时路径是本地缓存路径,只在当前设备有效,重启小程序就没了。
正确做法是:选择图片后先压缩、再上传到服务器的OSS(或者本地磁盘),服务器返回URL,前端再把这个URL提交给业务接口。需要注意的点有几个:
- 七牛云/阿里云OSS的跨域配置要正确,否则上传时浏览器会报跨域错
- 图片大小限制:微信小程序上传接口单次请求最大10MB,但实际建议压缩到500KB以内,汉服展示图一张高清图往往两三MB,不压缩很容易卡首页
- 图片名称不要用用户原来的文件名,容易重名,用
UUID拼接时间戳生成新文件名
我顺手写了一个小程序端的图片压缩工具函数,wx.compressImage配合wx.getImageInfo获取真实宽高,等比压缩到长边不超过1280像素,质量设为80,一张3MB的图能压到400KB左右。这个细节对于汉服这种"看图片下单"的业务特别重要,首图加载速度直接关系到转化率。
6. 项目交付内容的正确打开方式:源码、文档、视频怎么配合使用
这个项目的交付包里有源码、文档、运行视频和讲解视频四件套,很多拿到手的人第一反应是"四样东西我得全看一遍",其实不是。我的建议是有顺序地组合使用。
6.1 源码导入三步走
SpringBoot项目导入手比较固定,按下面三步基本不会出错:
- 环境准备:JDK 1.8以上(我用的1.8,太新的版本有一些SpringBoot老兼容问题),Maven 3.6+,MySQL 5.7+,Redis 6+,微信开发者工具最新稳定版
- 数据库初始化:项目里带了一个
schema.sql和data.sql,先执行建表,再执行初始数据插入。里面有我预置的测试数据,包括几个妆造师、十几套汉服款式、一些用户账号 - 启动后端:IDEA里改一下
application.yml的数据库连接配置和微信小程序appid/secret,直接运行main方法。然后打开微信开发者工具,导入小程序端项目目录,把app.js里的接口BaseURL改成后端地址(本地开发就用http://localhost:8080,真机调试需要用局域网IP)
这里有一个新手高频问题:小程序真机调试的时候请求不到本地的后端接口。原因是小程序的request合法域名校验——开发模式下可以在开发者工具里勾选"不校验合法域名",但真机预览时必须在小程序后台配置request合法域名,而且必须是HTTPS(开发时可以临时用调试模式绕过)。本地联调建议用开发者工具而不是真机,能省很多配置的麻烦。
6.2 文档和视频的分工
文档里重点看三部分数据库设计文档和接口文档。数据库设计文档里画了ER图和每张表的字段说明,接口文档里有每个接口的请求参数和返回示例,调试的时候配合Postman或者Apifox用非常方便。运行视频是录制了一整套从启动到用户下单的完整流程,它的价值是让你在没跑起来之前知道系统应该是什么样,避免"跑起来但不知道对不对"。讲解视频是按模块讲的,涵盖了架构设计、关键代码逐行解读、业务流程联动,适合跟着写一遍来真正掌握。
我的建议顺序是:先看运行视频了解全貌,再看文档里的数据库设计搭建环境,跑通之后再对照讲解视频学核心代码,最后自己动手改功能。这样学习的密度最高,不会一上来就被大段源码绕晕。
6.3 部署上线提前要做的事
如果这个项目要真正商用,交付包里的配置还不能直接拿来上线,有几件事必须提前做:
- 微信小程序正式上线要求域名必须是HTTPS且备案,不能再用IP访问
- 微信支付需要商户号申请和证书配置,交付包里用的是测试号模拟,正式环境得替换成真实的appid和商户密钥
- 数据库连接、Redis密码这些敏感信息不能写死在application.yml里,要用环境变量或者配置中心管理
- 图片上传要换正式OSS桶并参考加防盗链,不然图片被别人盗链会消耗你的流量费用
我是建议先拿这套系统做小规模试运营(比如先接3到5家合作门店),跑通一个月的真实订单数据,再根据运营反馈迭代下一版。这个业务有一个很好的点:SKU是明确的三维(款式×尺码×数量),不是像服装电商那样海量上新,运营数据模型简单清楚,所以系统逻辑和实际业务能对得上。
这套系统做完之后,我最大的感受是:做一个业务系统,技术难点其实不在某个算法或者某个API的细节上,而在把真实业务的约束条件转化成可靠的数据模型和状态流转。拿这个项目练手,能学到的比一般图书管理、宿舍管理系统要多得多,至少覆盖了支付、库存并发、小程序端联调、状态机设计这些工作中的高频场景。如果再往深走,后续可以扩展的方向也很多:积分商城、会员卡、满减活动、多门店协同、大数据分析用户偏好做智能推荐,这套架构的扩展性都能撑得住。
