每年一到校园文化节汉服社的仓库就跟打仗一样。桌子上的租借登记表密密麻麻写满了名字哪件齐胸襦裙被谁借走了、什么时候该还、押金扣了多少全靠人工翻本子。更别提热门尺码被重复预订、归还时发现破损没人认账这些事。被逼急之后我直接动手做了一套面向高校场景的汉服租赁管理系统技术栈锁定 SpringBoot Vue MyBatis MySQL后端用 Java 生态最稳妥的那一套前端用 Vue 做单页应用数据库设计上把库存、订单、用户、尺码全部拆清楚最后整理成了完整源码。这篇博文就把这套系统的设计思路、核心代码逻辑、数据库落地方式以及部署步骤从头到尾讲透适合正在做课程设计、毕业设计或者想在社团里真正落地一套租赁管理系统的同学参考。1. 高校汉服租赁的真实痛点这不是一个普通的商城系统1.1 社团管理汉服有多痛汉服租赁和普通商品售卖有个本质区别商品卖出去之后所有权就转移了但租赁品必须追踪“谁在用、用多久、什么时候归还、归还时完好程度”。高校社团场景里还要叠加几个特殊因素汉服尺码跨度大同一款式往往要备 S 到 XXL 好几个码每个码的库存数量还不一样。活动周期集中文化节、毕业照、传统节日前后订单量暴增靠微信接龙根本排不过来。押金和租金要分开算逾期费用还要按天计算人工统计容易扯皮。汉服价值不低破损、污渍、丢失都需要在订单里留痕。这些需求叠加在一起Excel 表格和纸质登记已经完全不够用了。于是系统里必须有一套清晰的“商品 SKU 订单状态机 库存联动”模型而不是简单套一个商城模板。1.2 核心角色与功能边界我接手这个项目之后第一件事不是写代码而是把用户角色和功能边界画清楚。这套系统最终只保留两个核心角色普通学生用户注册登录、浏览汉服、按分类和尺码筛选、查看详情与租金押金、提交租赁订单、查看个人订单状态。管理员汉服录入与上下架、库存管理、订单审核与状态推进、归还确认、用户管理、公告发布。没有把支付系统做成真的支付宝微信对接因为高校内部社团租赁往往涉及线下转账或者活动经费统一结算线上支付接口在这个场景里反而鸡肋。所以订单流程里保留了“待支付”状态允许管理员线下确认收款后手动变更状态。1.3 为什么坚持前后端分离而不是直接用 Java 模板引擎之前我也想过用 SpringBoot 自带的 Thymeleaf 一套搞定但很快放弃了。原因有三个前台展示页和后台管理页交互逻辑差异太大混在同一个模板工程里会让代码越来越乱。学生端需要比较好的浏览体验Vue 做单页应用在列表筛选、详情切换时明显更流畅也不需要整页刷新。后续如果要做微信小程序Vue 前端可以直接迁移大部分业务逻辑而后端接口完全不用动。前后端分离之后整个项目分成两个独立工程后端只负责接口和数据前端只负责交互和渲染开发时也可以并行推进。2. 数据库表结构设计把库存、订单、尺码一次想清楚2.1 核心表怎么拆数据库设计是这套系统最重要的一步。我第一版直接把库存数字写在汉服表里结果一个款式三个尺码就要拆成三条记录查询列表时还得额外聚合维护起来极其难受。后来重新设计成了六张核心表表名作用user用户表存放账号、密码密文、角色、联系方式costume汉服款式表存放名称、分类、封面图、日租金、押金、详情costume_sku汉服尺码库存表每件汉服款式按尺码拆成一行rental_order租赁订单主表记录用户、总金额、押金、起止时间、状态order_item订单明细表记录订单里具体租了哪件汉服、哪个尺码、数量announcement公告表用于发布社团通知这里最关键的拆法是把“款式”和“尺码库存”分成两张表。costume 表只存汉服本身的公共属性costume_sku 表存“这个款式下的某个尺码还有几件”。这样查询“有哪些汉服在租”和“某件汉服是否还有 L 码”都非常直接后续增加尺码或者补充库存也不影响主表数据。字段设计上要注意几个容易被忽略的地方user 表密码字段长度至少设 60因为 BCrypt 加密后字符串比较长设成 32 会直接报错。rental_order 要单独存 order_no 订单编号推荐用时间戳加随机数的格式方便线下对账。金额字段用 decimal(10, 2)不要用 float 或 double否则计算租金时会出现精度问题。create_time、update_time 用 datetime统一在代码里维护不要依赖数据库的自动更新。2.2 订单状态机数字字段背后的流程设计租赁订单和普通购物订单最大的区别是状态流转更多。我最终用的状态定义为状态值含义后续动作0待支付超时自动取消或管理员确认收款1待取货用户到线下指定地点领取汉服2租赁中管理员点击确认已取货后进入3待归还用户提交归还申请4已完成管理员验收确认押金退还逻辑结束5已取消订单关闭库存释放6已逾期超过应还日期未归还需要计算逾期费用为什么状态字段用数字而不是字符串一是存储体积小二是方便代码里做枚举判断。但前端展示时不能直接吐数字给用户需要在 VO 转换层把状态值映射成“待支付”“租赁中”这样的文案这一步一定要做否则用户看到订单状态是“2”会一脸懵。状态流转只允许“顺序推进 有限回退”比如待支付可以取消但租赁中不能直接跳回待支付。我在 Service 层写了一个状态校验方法每次更新前先判断旧状态是否合法避免接口被直接调用时跳过业务流程。2.3 库存扣减与防超卖一行 SQL 解决的问题租赁系统的库存问题比普通商城更敏感。普通商品超卖最多退款租赁品超卖会导致活动当天拿不到衣服这在学校里是要出事故的。我一开始用的逻辑是先查库存判断大于 0 再减结果两个人同时下单时很容易超卖。后面改成了一条 SQL 解决UPDATE costume_sku SET stock stock - 1 WHERE id #{skuId} AND stock 0;这个 UPDATE 语句执行后返回受影响行数如果返回 0 就说明库存不足直接提示用户。数据库行锁保证了同一时刻只有一个请求能真正扣减成功不需要引入 Redis 分布式锁也能解决大部分并发场景。订单取消或超时释放库存时则执行反向操作UPDATE costume_sku SET stock stock 1 WHERE id #{skuId};这套方案在课程设计和中小型社团场景下完全够用等真正到了几千人同时抢租的体量再考虑引入 Redis 缓存预扣库存的方案。3. SpringBoot MyBatis 后端落地分层、事务与核心接口3.1 项目分层和包结构后端工程我用的是标准的分层架构包结构如下com.example.hanfu ├── controller # 接口层只负责参数接收和结果包装 ├── service # 业务层事务和业务规则全部在这层 ├── mapper # MyBatis Mapper接口 ├── entity # 数据库实体 ├── dto # 接收前端参数的封装对象 ├── vo # 返回前端的结果对象 └── config # 配置类JWT、CORS、拦截器Controller 层只做三件事接收参数、调用 Service、包装返回值。业务逻辑不要写在 Controller 里否则后面加一个管理员操作日志功能时你会发现每个接口都要改一遍。Service 层是核心事务注解放在这一层因为一个业务操作往往要连续调用好几个 Mapper 方法比如下单要同时插入订单表、扣减库存、生成订单明细任何一个环节失败都应该全部回滚。3.2 MyBatis XML 映射与动态 SQL为什么要用 MyBatis 而不是直接用 JPA原因很直接这个系统里有大量动态查询条件比如按分类、尺码、价格区间、状态筛选汉服用 JPA 的 Specification 写起来很绕而 MyBatis 的 XML 里拼动态 SQL 非常直观。分页是我当时重点处理的需求。我用的方案是 PageHelper 分页插件用法很简单PageHelper.startPage(pageNum, pageSize); ListCostumeVO list costumeMapper.selectCostumeList(query); PageInfoCostumeVO pageInfo new PageInfo(list);但这里有一个必须记住的坑PageHelper.startPage()后面只能跟一条 Mapper 查询语句如果中间穿插了其他查询分页条件就会作用于错误的 SQL。比如列表页需要同时查询分类名称如果你先查了分类表再查汉服表分页就失效了而且还会产生一条多余的 count 查询。XML 里动态 SQL 的典型用法大家可以直接参考这个结构select idselectCostumeList resultTypecom.example.hanfu.vo.CostumeVO SELECT c.id, c.name, c.category, c.daily_price, c.deposit, c.cover_image, c.status, c.description FROM costume c where if testcategory ! null and category ! AND c.category #{category} /if if testkeyword ! null and keyword ! AND c.name LIKE CONCAT(%, #{keyword}, %) /if /where ORDER BY c.create_time DESC /select注意#{keyword}使用的是预编译占位符可以防止 SQL 注入最好不好用${}拼接字符串。3.3 登录鉴权JWT 而不是 Session前后端分离之后Session 方案会遇到跨域和分布式部署的麻烦所以登录这块我直接用 JWT。流程是用户提交用户名密码Service 层调用 BCryptPasswordEncoder 校验密码密文。校验通过后生成 JWT Token把用户 id 和角色塞进 claims。前端把 Token 存在 localStorage每次请求时放在 Authorization Header 里。后端写一个拦截器解析 Token把用户信息放入 ThreadLocal。JWT 的密钥要放在 application.yml 里不要硬编码到代码中长度建议至少 32 位。还要设置合理的过期时间学生用户一般一次登录用一天我设的是 24 小时到期后重新登录。String token Jwts.builder() .setSubject(String.valueOf(user.getId())) .claim(role, user.getRole()) .setExpiration(new Date(System.currentTimeMillis() 86400000)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();3.4 下单接口事务边界放在哪下单是整个系统里最典型的“多步写操作”场景我拆成了以下步骤校验用户是否登录。校验汉服 SKU 是否存在、状态是否上架。计算租金和押金总额生成订单号和订单明细。扣减库存使用UPDATE ... WHERE stock 0检查是否成功。插入订单记录和订单明细记录。如果第 4 步失败抛异常触发事务回滚。Service 层方法上加Transactional(rollbackFor Exception.class)这里必须指定 rollbackFor。SpringBoot 的Transactional默认只对 RuntimeException 回滚如果代码里用 try-catch 把异常吞掉了事务根本不会生效。下单接口的返回结果不要只返回一个 boolean而是返回一个包含订单号、应付金额、预计取货时间的结果对象这样前端结算页能直接展示用户体验会好很多。4. Vue 前端设计从页面组件到接口联调4.1 目录结构与开发环境前端用 Vue 2 Vue Router Vuex Element UI 搭起来也可以直接用 Vue 3 Vite 重写不过当前这套源码用的是 Vue 2 稳定方案因为很多高校课程设计环境里 Node 版本比较旧。项目核心目录src ├── api # 接口请求封装 ├── assets # 静态资源 ├── components # 公共组件 ├── router # 路由配置 ├── store # 状态管理 ├── utils # 工具函数 ├── views # 页面组件 │ ├── Home.vue │ ├── CostumeList.vue │ ├── CostumeDetail.vue │ ├── OrderConfirm.vue │ ├── UserCenter.vue │ └── admin # 管理后台页面开发环境安装依赖时我建议直接使用 npm配合淘宝镜像安装速度会快很多npm install -g cnpm --registryhttps://registry.npmmirror.com cnpm install4.2 核心页面和组件划分首页设计上参考了电商网站的思路顶部是搜索栏和导航栏中间是热门汉服轮播图下面是分类入口。汉服列表页用卡片式布局每张卡片显示封面图、名称、日租金、押金和剩余尺码点击卡片跳转详情页。组件划分要遵循“一个组件只干一件事”的原则。我当时划分了几个关键组件CostumeCard汉服卡片用于列表页复用。SizeSelector尺码选择器根据后端返回的 SKU 列表动态渲染。DateRangePicker租赁时间选择器需要限制最早取货日期不能早于今天。OrderStatusTag订单状态标签根据状态值显示不同颜色。这里有一个比容易忽略的细节租赁时间选择器选完日期后前端要计算租赁天数并实时显示总租金。后端接口只接收起止日期不能直接信任前端传过来的总金额下单时后端必须根据起止日期重新计算。否则用户手动改一下请求参数就能用一天的价格租十天。4.3 axios 拦截器与跨域处理前端所有请求我统一封装在一个 request.js 文件里核心逻辑是 axios 实例加上请求和响应拦截器。service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }) service.interceptors.response.use( response { const res response.data if (res.code ! 200) { this.$message.error(res.msg) return Promise.reject(new Error(res.msg)) } return res }, error { if (error.response.status 401) { router.push(/login) } return Promise.reject(error) } )本地开发时的跨域问题通过 Vue CLI 的 devServer 代理解决devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端请求/api/costume/list开发服务器会自动转发到后端 8080 端口不需要后端开启 CORS也避免了本地调试时被跨域拦截。4.4 管理后台的实现要点管理后台我没有单独做一套布局而是使用 Vue Router 的子路由在 AdminLayout 组件里通过侧边栏菜单切换页面。后台核心页面包括汉服管理表格展示所有款式支持新增、编辑、上下架图片上传用 Element UI 的 Upload 组件。库存管理按款式维度展示所有尺码库存支持手动补库存。订单管理订单列表、状态筛选、订单详情、状态推进按钮。用户管理查看注册用户列表、重置密码。后台列表页全部使用分页表格后端返回 PageInfo 结构。前端每次切换页码时重新请求数据数据量不大时没必要做前端静态分页。5. 完整源码跑通与部署本地环境和服务器配置5.1 环境版本统一这套项目可以跑通的前提是环境版本别乱用我实际验证过的组合如下组件版本JDK1.8 或 11Maven3.6 以上MySQL5.7 或 8.0Node.js14 以上SpringBoot2.3.7.RELEASEVue CLI4.5.0SpringBoot 版本不建议选太高的之前试过 SpringBoot 3.x发现一些老版本的 MyBatis Starter 不兼容启动直接报错还要额外适配 Jakarta 命名空间。这个项目里用 2.3.7 配合 mybatis-spring-boot-starter 2.2.0 是最稳的。5.2 后端启动流程导入项目到 IDEA 之后先改数据库配置。在 application.yml 里确认以下内容spring: datasource: url: jdbc:mysql://localhost:3306/hanfu_rental?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver数据库连接串里的 serverTimezone 一定要配否则用 8.0 版本的 MySQL 驱动时会报时间时区错误。字符集使用 utf8避免中文乱码。然后执行项目附带 database.sql 脚本把表结构和初始数据导入数据库。脚本里除了建表语句我放了一些测试用的汉服数据和管理员账号方便第一次跑通。表结构初始化之后直接运行 Application 启动类看到控制台输出 Tomcat started on port(s): 8080 就说明后端成功了。5.3 前端启动流程前端目录下执行npm install npm run serve启动之后访问 http://localhost:8081 前端默认端口我设置成 8081避免和后端 8080 冲突。如果报端口占用可以在 vue.config.js 里修改。第一次跑通时建议先注册一个学生账号再去管理后台添加汉服和库存然后到前台完成一次完整的下单流程。这个端到端的验证过程能帮你确认环境是否全部通了。5.4 Nginx 反向代理部署到服务器本地跑通之后部署到服务器我采用的方式是前端打包成静态文件交给 Nginx后端打成 jar 包跑在服务器上。前端打包npm run build得到 dist 目录上传到服务器的 /usr/share/nginx/html/hanfu 目录。Nginx 配置做两个事情静态文件访问直接指向 dist 目录。/api开头的请求反向代理到后端 8080 端口。关键配置如下server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html/hanfu; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }后端部署更简单运行打包好的 jarnohup java -jar hanfu-rental-0.0.1-SNAPSHOT.jar app.log 21 注意关闭服务器的防火墙端口限制确认 8080 端口只对内网开放对外只放行 80 端口。6. 我在实际开发中踩过的坑和后续升级建议6.1 图片上传后访问不了的常见原因汉服管理里需要上传封面图我一开始是直接把图片保存到项目本地的 static 目录结果部署到服务器之后发现图片全部加载不出来。原因是 SpringBoot 打成 jar 包后static 路径在 jar 内部无法动态写入。解决方案有两种把图片存放路径改成服务器磁盘上的绝对路径比如 /data/hanfu/images然后通过配置类映射为静态资源路径。有条件的话直接用对象存储服务业务代码只存 URL。考虑到这个项目主要跑在校园内网我选择的是第一种成本最低。6.2 订单超时未支付导致的库存悬挂如果用户下单后一直不支付库存被扣减了但订单永远挂在待支付状态其他同学就租不到这件衣服。我的处理方案是写一个定时任务每分钟扫描一次超过 30 分钟未支付的订单自动将其改为已取消并释放库存。Scheduled(cron 0 0/1 * * * ?) public void autoCancelExpiredOrders() { orderMapper.cancelExpiredOrders(new Date(System.currentTimeMillis() - 30 * 60 * 1000)); // 同时恢复对应SKU库存 }当时还考虑过用延迟队列更精准但考虑到系统体量定时任务足够而且实现起来简单很多。6.3 MyBatis 缓存与分页插件的坑MyBatis 的二级缓存我明确没有开启因为租赁系统的数据实时性要求很高尤其是库存字段缓存稍微过期就会造成超卖。如果你非要开缓存至少要把库存相关的 Mapper 设置为 useCachefalse。分页插件还有一个常见问题PageHelper 在嵌套结果映射的分页查询中会失效因为分页是在执行 SQL 之后附加 LIMIT而嵌套结果映射会把多个结果合并导致总记录数不准确。所以我在这套系统里统一用“先分页查主表再查关联信息”的方式避免使用复杂的嵌套查询。6.4 从课程设计到企业级还差什么这套源码命名为“企业级”并不是说代码完美到可以直接支撑大流量上线而是指架构分层、数据库设计、事务控制和权限校验这些方面都按照企业项目的规范来写。如果要真正在商业环境运行还可以进一步升级的点包括引入 Redis 缓存热点数据和分布式锁。引入消息队列处理订单异步消息。引入 Spring Security 替换手动拦截器。增加操作日志与数据统计报表。对接真实支付渠道并增加退款流程。但单纯从高校场景来看当前这套架构已经具备很强的参考价值代码清晰、模块边界明确无论是拿来做课程设计还是作为毕设原型扩展起来都相对容易。我在实际开发里最大的体会是这种管理系统的难点其实不在某个技术点而在于把业务流程理清楚。库存怎么扣、订单状态怎么流转、租赁时间怎么计算这些想明白之后SpringBoot、Vue 这些技术都是熟能生巧的事。如果你也正在做一个类似的管理系统建议先画一张业务状态图再开始写代码一定能少走很多弯路。
企业数字化 ERP 产品动态
相关推荐
Bi-LSTM+FastText轻量级舆情情感分析实战 简介:本资源是一份面向人工智能与自然语言处理初学者的高分课程设计实践项目,聚焦网络舆情情感分析任务,融合Bi-LSTM深层语义建模与FastText词向量表征能力,适用于本科课程设计、期末大作业及NLP入门实战。压缩包共18个文件&#… · 2026/9/24 20:31:41
配电网韧性提升:MPS预配置两阶段随机规划及Matlab实现 这个标题一出来,做配电网方向的人应该都能get到重点:极端事件下,移动电源到底该提前放在哪、放多少,才能在灾后快速恢复关键负荷,同时又不至于白花钱。我当初复现这类SCI一区论文时,最大的卡点反而不是那些… · 2026/9/24 20:31:41
安卓开发工程师技术栈与面试指南:从基础到AI大模型接入 不知道你现在是正准备入行的新人,还是已经写了两三年业务代码想跳槽的工程师。我这两年陆陆续续帮团队筛简历、做技术面试,自己也跳过一次槽。一个很强烈的感受是:“安卓开发工程师”这个职位的边界,和很多人以为的已经不太一样了… · 2026/9/24 20:31:41
SpringBoot+Vue+MySQL高校实习管理系统设计与实现全解析 说实话,每年到了毕业季,总有一批计算机专业的学生被“实习管理系统”这类题目折磨得焦头烂额。这题目看起来传统,但真要做得像样,前后端技术得打通、业务逻辑得理顺、论文还得凑够字数,确实不轻松。我自己在带毕设和做… · 2026/9/24 21:10:23
ZFS文件系统实战指南:从存储池、数据完整性到快照备份 前几年我在折腾一台老服务器时,数据盘莫名奇妙丢了一个目录里的几百张照片,当时用的还是ext4,事后查了半天也没找到确切原因,只知道硬盘SMART一切正常,文件却像被什么东西啃掉一块。后来换了ZFS文件系统,同… · 2026/9/24 21:10:23
C语言scanf完全指南:从输入原理到实战避坑 很多初学者在学会printf之后都会卡在同一道坎上:程序倒是能往外输出了,但只能“自言自语”。写来写去都是固定几行字,你问程序什么,程序一概听不见。C 语言里的scanf函数要解决的就是这件事——让程序真正接收用户输入的数据。这一… · 2026/9/24 21:10:23
C语言逻辑量与分支语句:从逻辑运算符到if/switch实战解析 1. 内容整体设计与思路拆解1.1 这个项目标题到底在说什么"C语言 逻辑量、逻辑运算符和逻辑表达式、if语句和switch语句"——这个标题放在一起看,其实覆盖的是C语言里"从判断到分支"的完整链条。很多初学者一上来就把逻辑运算符当成数学里的&quo… · 2026/9/24 21:10:23
一条命令批量生成100条视频:Hypit多Agent视频生产管线实战 1. 从一条命令说起:这个开源项目到底在解决什么问题第一次看到“一条命令复刻100条爆款视频”这个说法,我的反应是:要么是标题党,要么背后有一套相当成熟的模板化生产管线。花了两天把项目源码和配套的Agent工作流跑通之后&#x… · 2026/9/24 21:10:23
QUALITY_SCORE.md 完全ガイド:エージェントファーストなリポジトリの品質追跡を実装する QUALITY_SCORE.md 完全ガイド:エージェントファーストなリポジトリの品質追跡を実装する 【免费下载链接】learn-harness-engineering Harness engineering beginner tutorial, from 0 to 1 项目地址: https://gitcode.com/gh_mirrors/le/learn-harness-engineeri… · 2026/9/24 21:10:10
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44