ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于Spring Boot和Vue的摄影设备租赁管理系统设计

2026/9/26 7:35:34 拓冰建站 浏览量
基于Spring Boot和Vue的摄影设备租赁管理系统设计 1. 项目背景与整体设计思路1.1 为什么需要这样一套系统摄影设备租赁在影楼、独立摄影师、高校摄影社团和自媒体小团队里一直是个高频需求。我接触到这个项目是帮一个本地器材租赁工作室做系统。他们之前的运营模式很原始用Excel表格登记设备借出、归还、押金、租金设备一多就开始出各种问题。比如同一台镜头被两个客户同时预订押金算错归还日期记混设备损坏找不到对应订单记录。这其实不是个例很多小型租赁商都有同样的痛点。他们当时最迫切的需求并不是做一个花哨的线上商城而是把“设备上架、客户租赁、费用结算、归还验收”这条核心链路理顺。换句话说他们要的是一个真正的后台管理系统核心是“管”不是“卖”。我需要让设备状态实时可知解决“这台机身现在到底在谁手里”“这台镜头是不是该送去保养了”“这个订单明天到期该催还了”这些日常问题。把这些需求列清楚之后我决定按一套标准的管理系统来设计而不是上来就写代码。整个系统的核心模块大概覆盖这几块用户管理管理员端和普通租赁用户端、设备管理类型、品牌、型号、库存、图片、日租金、租赁订单管理下单、租期计算、费用明细、归还验收、统计看板热门设备、营收情况。只有把这些业务模块理清后续前后端开发才不会走偏。1.2 技术选型解析——为什么是 Spring Boot Vue技术选型是我最先拍板的事。后端我毫不犹豫选了 Java Spring Boot理由很现实第一团队对 Java 技术栈熟悉出了问题能快速定位第二Spring Boot 的自动配置和生态能把我从繁琐的 XML 配置里解放出来让我专注写业务逻辑第三项目要做成可交付的管理系统Spring Boot 在部署、监控、权限集成方面都有比较成熟的方案。我没有用太新的版本选的是 Spring Boot 2.7 这个稳定版本搭配 MyBatis-Plus 做数据持久层。为什么不直接用 MyBatis 原生主要考虑到这个系统里有大量单表 CRUD 操作MyBatis-Plus 的 BaseMapper 能少写差不多一半的 Mapper 代码。对于复杂的多表查询我还是保留了 XML 自定义 SQL 的方式灵活性和开发效率都能兼顾。数据库用的 MySQL 8.0存储引擎选 InnoDB因为业务里涉及订单和库存需要支持事务。前端部分我选了 Vue 3 Vite Element Plus。Vue 3 的 Composition API 在组织代码逻辑上比 Vue 2 的 Options API 更清晰尤其像设备列表中那种筛选、分页、多状态切换的交互用 setup 函数和组合式函数可以把逻辑抽出来复用体验很好。Element Plus 组件库几乎能覆盖后台管理页面需要的表格、表单、弹窗、日期选择器、上传组件不用自己重复造轮子。这套技术组合对于“管理后台”这类项目属于低成本高回报的选择。2. 数据库设计与业务模型拆解2.1 核心实体关系与字段设计数据库设计是整个项目的骨架前期我把大量时间花在这里。实体关系并不复杂核心有四个表用户表、设备表、租赁订单表、设备归还验收单表。再加上辅助的设备分类表、公告表、操作日志表。用户表的字段除了常见的用户名、密码、手机号还特意加了一个“角色区分”的字段我用的是 role 字段0 代表管理员1 代表普通租赁用户。密码存储用的是 BCrypt 加密不是 MD5这一点是常识但也容易被忽视千万不能把明文密码存到数据库里。设备表的字段要稍微说得细一点因为这是租赁业务的核心资产。字段包括设备编号、分类ID、名称、品牌、型号、图片URL、库存总量、可租库存、日租金、押金、状态。很多新手容易忽略“设备编号”这个字段但从实际运营角度租赁方需要在归还时扫码或手输编号快速定位设备编号的存在能避免同型号多台设备区分不开的问题。可租库存和库存总量我采用了冗余字段设计方便查询列表时直接展示而不是每次都要去订单表里聚合计算。租赁订单表是业务逻辑最重的一张表。字段包括订单编号、用户ID、设备ID、租赁开始日期、租赁结束日期、租用天数、日租金单价、押金、订单总金额、订单状态、创建时间、支付时间。订单状态我用整数表示0 待支付、1 已支付待取件、2 租赁中、3 已归还待结算、4 已结算完成、5 已取消。这里的状态设计一定要想清楚因为前端页面的按钮显示、后端接口的权限控制全靠这个字段驱动。2.2 设备状态与租金的业务细节设备状态在租赁系统里是个容易理解错的地方。我一开始只设计了“在库”“租出”两种状态后来发现不够。设备还会有“维修中”“已下架”的状态。一台镜头送去官方售后维修可能需要两周这段时间它既不在库也不在别人手里如果没有独立的“维修中”状态运营人员只能在备注里写文字时间一长就乱了。所以我把设备主状态和库存逻辑分开设备主状态用于展示和管理包括在库、已租出、维修中、已下架库存数量则通过设备表里的可租库存字段配合订单表来动态维护。下订单时扣减可租库存取消或归还时回补库存这是最简单的方案但必须在事务里做否则高并发下很容易超卖。租金的计算也是我仔细想过的。日租金乘以租用天数看起来简单但“租用天数”怎么算却有讲究。是按自然日算还是按24小时算如果客户今天上午9点取走后天晚上8点归还按自然日算是三天按小时算是近两天。我最终采用取整天的方案租赁开始日和结束日各算一天也就是计算两个日期之间的天数差加一再乘以日租金。这样做的好处是运营人员在沟通时容易解释用户也容易理解。押金逻辑我也梳理了一把。押金是在下单时冻结或者收取的我这边做的是在下单支付时一并收取押金归还设备验收无异常后押金原路退回。这里用订单表里的 deposit_amount 字段记录不需要额外做财务系统但需要在订单结算时把押金状态一并更新。这个项目里我不接入第三方支付而是做了“线下支付确认”流程管理员在后台点击确认收款这样也更贴近小型租赁工作室的实际操作场景。3. 后端开发的关键实现3.1 登录鉴权与接口权限控制后端开发我按分层结构来组织Controller层负责接收请求和参数校验Service层处理业务逻辑Mapper层管SQL。整个项目里我首先实现的是用户登录和鉴权因为后续所有接口都得靠它来保护。登录方案我用的是 JWT 配合拦截器。用户输入用户名密码后端校验通过后生成一个 token 返回给前端前端把 token 存到 localStorage每次请求时放在 Authorization 请求头里。后端写了一个拦截器统一拦截 /api/** 下的请求解析 token 并检查用户身份。用户角色不同接口权限也不同。比如删除设备、审核订单这类操作只有管理员能做普通用户只能查询设备和创建自己的订单。权限控制我是用拦截器加自定义注解实现的定义了一个 RequireAdmin 注解打在需要管理员权限的 Controller 方法上拦截器里读取注解并判断当前用户角色。这种方式比起依赖 Shiro 或 Spring Security 要轻量很多适合这种角色数量少的系统。PostMapping(/device/add) RequireAdmin public ResultString addDevice(RequestBody Valid DeviceAddDTO dto) { deviceService.addDevice(dto); return Result.success(添加成功); }说到安全我还在全局加了一个参数校验用 Spring Boot 的 Valid 注解配合自定义校验规则避免非法参数进入业务层。对输入数据的把控一定要做不能信前端已经校验过了很多安全问题都是因为后端接口裸奔导致的。3.2 租赁订单闭环的业务逻辑订单流程是后端业务逻辑里最核心的部分我按下单、支付、出库、归还、结算这几个环节逐步实现。先说下单用户在前端选择设备、选择租期、提交订单后端要做几件事校验设备是否存在且状态正常、校验租期是否正确、计算总金额、检查可租库存是否充足然后扣减库存、创建订单。库存扣减这块我用了数据库层面的乐观锁机制。在设备表里加一个 version 字段更新可租库存时用带版本条件更新的 SQL如果更新影响行数为0说明并发情况下别人已经把库存改了就返回“库存不足或者操作冲突”。这里直接使用 update 语句来保证原子性比在代码里先查询再更新要安全得多。UPDATE device SET stock_available stock_available - 1, version version 1 WHERE id #{deviceId} AND stock_available 0 AND version #{version}订单取消逻辑也要考虑。已支付的订单如果用户申请取消且设备还没出库就关闭订单并回补可租库存。如果是租赁中的订单提前归还就要走结算环节计算实际租用天数如果实际天数比预期天数短要退还多收的租金如果超期要补收逾期部分的费用。这块我抽象成了一个计算器类把所有金额计算逻辑集中在一起方便维护和测试。public BigDecimal calcSettlementAmount(Order order, LocalDate actualReturnDate) { long actualDays ChronoUnit.DAYS.between(order.getStartDate(), actualReturnDate) 1; BigDecimal actualAmount order.getDailyRent().multiply(BigDecimal.valueOf(actualDays)); return actualAmount.subtract(order.getOrderAmount()); }使用 BigDecimal 而不是 double 来计算金额是干这行最基本的自觉。double 在浮点运算中会丢失精度涉及钱的地方必须用 BigDecimal。3.3 文件上传与图片静态资源处理设备图片的上传是管理系统绕不开的功能。前端 Element Plus 的上传组件把文件发送到后台后端用 MultipartFile 接收。我这边对上传做了几个限制图片大小不超过5MB格式只允许 jpg、png、webp存储路径统一放在服务器的指定目录里。关于文件访问有个比较常见的坑如果 Spring Boot 项目的静态资源配置不正确上传的图片通过 URL 访问会返回 404。我在启动类里实现了 WebMvcConfigurer 接口通过 addResourceHandlers 方法把本地磁盘目录映射到 /images/** 访问路径。这样才能保证前端 img 标签可以直接通过/images/20240601/xxx.jpg的方式加载图片。http://localhost:8080/images/20240601/abc.jpg图片文件名我没有用原始文件名而是用时间戳加随机UUID重新命名避免重复和中文文件名导致的编码问题。存储目录按日期分文件夹方便后续迁移和清理。4. 前端 Vue 项目的落地实现4.1 页面框架与核心组件设计前端项目的整体框架我按照后台管理系统的常规布局来做左侧是菜单栏顶部是用户信息和退出登录中间是内容区域通过 Vue Router 控制页面切换。路由使用动态路由配置根据用户登录后的角色动态加载管理员页面或普通用户页面这样前端层面就已经做了一层权限隔离。设备列表页是系统里信息量最大的页面。表格展示设备编号、名称、分类、日租金、押金、状态、可租库存右上角提供关键词搜索和设备分类筛选左上角是新增设备按钮。这个页面我拆成了几个组件DeviceTable 负责表格渲染DeviceSearchBar 负责筛选条件DeviceFormDialog 负责新增和编辑弹窗。这样拆分的好处是如果后续要加批量导入、价格批量调整之类的功能改组件内部逻辑就行不会影响到其他页面。订单页面对我来说交互逻辑最复杂。用户端有“我的订单”页面展示自己下过的所有租赁单支持取消当前订单、申请续租、归还登记这类操作。管理员端有一个“订单管理”页面展示所有用户的订单支持按状态筛选、确认收款、审核归还请求。这里我用了一个 tab 切换来区分订单状态待支付、租赁中、已归还、已完成各占一个 tab每个 tab 下的数据通过传参调用同一个接口接口内部按状态字段查询。表单校验方面我自己没有写一堆 if else而是用 Element Plus 的表单校验规则。比较典型的例子是租赁日期选择器用户选择的结束日期不能早于开始日期就能用 validator 自定义校验方法实现。登录注册页也做了密码强度校验至少8位且包含字母和数字这些都是前端体验的一部分。4.2 前端请求封装与接口联调前端所有的 HTTP 请求我统一封装成了一个 request.js 模块基于 axios 封装实例。这样做的核心目的是统一处理三件事请求拦截器自动携带 token、响应拦截器统一处理业务错误码、请求超时的默认配置。// request.js 核心思路 const service axios.create({ baseURL: /api, timeout: 15000 }) 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 401) { // token 失效跳转登录页 } return res }, error { // 统一提示 } )前后端联调时遇到最多的接口对接问题就是日期数据格式不一致。后端 LocalDateTime 序列化后默认格式是 “2024-06-01T10:30:00”前端组件回显时总报错。解决方式是后端在 application.yml 里统一配置 JSON 序列化格式前端这边也用 dayjs 做日期解析两边约定好格式这类问题基本能消除。跨域问题在开发环境倒是没折腾太久。Vite 配置了代理把/api开头的请求转发到后端http://localhost:8080生产环境则由 Nginx 做反向代理。我在本地开发时是这么配置 vite.config.ts 的server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }4.3 状态管理方案选型前端状态管理我用的 Pinia因为项目从一开始就是 Vue 3Pinia 的 Composition 风格和 Vue 3 天然契合。在这个系统里我需要用全局状态保存的核心数据只有用户登录信息和菜单权限其余业务数据都是通过接口实时获取的不需要为了用状态管理而把页面数据硬塞进去。系统当前登录用户的昵称、角色、头像存到 Pinia然后在侧边栏组件、头部组件、个人中心页面里读取。用户退出登录时调用退出接口后清空本地存储和 Pinia 中的数据再跳转回登录页。比 Vuex 繁琐的那些 mutations、actions 概念Pinia 简化了很多直接定义 store 里的变量和函数在组件里 import 进来直接用开发体验确实好不少。5. 实操中遇到的坑与排查记录5.1 常见问题速查表下面这些问题是开发前后端项目时最常遇见的我直接整理成一个速查表基本都能对号入座。问题现象可能原因解决思路前端请求报 404后端接口路径或请求方式不匹配检查 Controller 的 RequestMapping 和前端 axios 请求路径留意 GET/POST 是否对应上传图片后访问 404Spring Boot 未配置磁盘映射实现 addResourceHandlers 映射本地目录到 /images/**日期显示带字母 T前后端日期格式未统一配置后端全局 Jackson 格式或前端格式化处理登录后刷新页面失效token 存储或路由守卫判断问题检查 localStorage 写入是否正确路由守卫对 token 做持久化判断库存数据不对并发下单时库存扣减不是原子操作换用乐观锁或行级锁更新库存请求接口返回 CORS 跨域开发环境端口不同配置 Vite 代理或在后端配置跨域过滤器5.2 几个有代表性的 Bug 复盘第一个让我印象深刻的 Bug是设备列表页面在用户点击“上下架”按钮后前端列表不刷新。原因是页面只在 mounted 生命周期里请求了一次接口而上下架操作之后没有重新调列表接口。这个问题虽然简单但它提醒我全系统的“操作完成后刷新列表”逻辑要统一处理。后来我写了一个 useTableRefresh 组合式函数封装了“请求数据—操作—重新加载”的流程类似交互都走同一个逻辑。第二个 Bug 是订单结算金额偶尔出现 0 或者负数的情况。排查发现是 BigDecimal 除法计算时丢失精度导致单位租金换算出错。我在租金计算工具类里改了分账粒度先把金额换算成分再计算最后再转回元问题就没再出现。第三个 Bug 比较隐蔽管理员在后台修改了某台设备的日租金但是已经创建的订单没有受影响。这其实不算 Bug而是业务规则租赁单一旦创建金额就应该锁定不能因为设备价格后续调整而变化。我在订单表里冗余存储了日租金单价字段价格修改只影响新订单老订单结算时用的是创建时的快照值。这块如果不在设计阶段考虑清楚后面就会很被动。5.3 回归测试与数据模拟我这边没有很正式的自动化测试环境主要靠手动操作加接口测试工具。开发接口阶段用 Postman 把请求和预期结果整理成集合每个模块改动后先跑一轮核心流程。比如涉及订单模块改动就要验证“下单—支付—出库—归还—结算”整条链路任何一环出错都要回查。为了测试方便项目里还加了一个初始化数据的 SQL 文件内置了若干测试账户和测试设备每次新建环境跑一遍脚本就有数据可用。测试数据我特意用了接近真实的设备信息比如索尼 A7M4、佳能 RF 24-70mm F2.8、大疆稳定器这样页面效果直观也方便演示给需求方看。6. 项目复盘与个人建议6.1 这个项目学到什么这套系统做完之后我最大的体会是做业务系统最重要的不是技术有多新而是搞清楚业务的边界和异常场景。比如租赁中的续租是否要重新计算押金、设备损坏的维修费用怎么和押金抵扣、订单取消的手续费规则是什么这些需求方的脑海里其实不一定有明确答案需要开发和运营人员一起梳理清楚然后固化成代码逻辑。技术上我把 Spring Boot 的很多基础设施能力又熟练了一遍尤其是事务管理、统一异常处理、参数校验、全局日志。这些能力在平时小项目里可能用不全但在这种业务管理系统里非常关键。前端也让我把 Vue 3 的组合式 API 用得更顺手了把列表页、表单页、详情页的通用逻辑抽成组合函数代码复用率明显提升。这中间还要特别强调一下交付和沟通的体验。系统的使用者是非技术人员他们对系统的操作习惯和程序员不一样所以在写代码之外我用了不少精力去调整一些交互细节大按钮、明确的状态提示、操作成功后的反馈这些小改动反而让需求方认为系统很专业。6.2 这套系统的后续扩展方向目前这套系统的核心租赁流程已经跑通了但如果真想上线商用我觉得有四个方向值得继续迭代。一个是接入支付体系。当前采用的是线下确认收款的方式正式运营还是需要对接微信支付或支付宝订单状态从待支付到已支付的流转才能全自动完成。第二是增加消息提醒能力。租期临近结束、设备归还逾期、押金退款到账这些节点如果有短信或者微信模板消息通知运营会省心不少。这个可以用消息队列去实现但当前用户量还没有那么大直接用 Spring Boot 里的定时任务扫描订单表并调用通知接口就足够了。第三是设备维保管理。相机和镜头这类精密器材使用频次高了之后需要定期清洁和保养系统里可以给每台设备增加一个累计租赁次数和保养记录表累计到一定次数自动打上“需保养”标记。第四是把统计报表做细。设备租赁属于典型的低频高客单业务老板最关心的通常是各种设备一个月租了多少次、收入贡献占比是多少、哪类设备空置率最高。增加一个简单的数据看板页面用图表展示营收趋势和设备出租率整个系统的价值就会提升一大截。根据我个人经验做这类管理系统切忌一上来就追求功能大而全最稳妥的路线是先跑通核心业务闭环再通过用户反馈去迭代细节。摄影设备租赁的本质是帮助经营者把线下纸面记录变成可追踪的数字化流程把这个点做到位就已经解决了80%的问题。剩下的都是让这个系统变得更顺手、更自动化的加分项。