
这两年露营热度一直没降下来尤其是周末和节假日朋友圈里几乎人人都在晒帐篷、天幕、折叠椅。但真要自己置办一套完整的露营装备开销并不小——一顶像样的帐篷几百上千加上防潮垫、睡袋、营地灯、折叠桌椅、烤炉、推车全套配齐轻松破五千。很多人一年也就露营两三次买回来大部分时间在储物间吃灰于是露营装备租赁这个需求就变得非常真实。这个基于SpringBoot的露营活动装备租赁系统核心就是把装备的在线搜索、预订、支付、出库、归还、押金退还、信用评价这条完整链路做通。我把它做完之后最大的感受是它的难点不在于某个单点功能有多复杂而在于把租赁业务状态机理清楚、把账算明白同时把数据一致性和并发问题处理好。这篇文章我就完整复盘一下这个系统的设计思路、表结构、核心业务实现和踩过的坑给准备做类似项目的同学一个可以直接参考的蓝本。1. 项目定位与核心需求拆解1.1 露营经济兴起背后的装备租赁逻辑露营装备租赁这个需求本质上跟共享经济其他领域一样本质是“使用权和所有权分离”。对于平台来说需要同时管理两类核心资源一类是装备本身的库存状态另一类是用户的资金流和信用状态。装备具有明显的非标准化属性——同样是帐篷有双人帐、家庭帐、隧道帐单价和押金差别很大同样是睡袋有的适合夏季、有的适合零下环境。所以系统不能像图书管理系统那样只用“数量”来管理库存必须对装备做精细化分类和规格管理。另一个现实因素是季节性。露营有明显的淡旺季周末和法定节假日订单量是平时的几倍。如果按一锤子买卖的思路做系统只在高峰期能扛住订单也可以但如果要做成可长期运营的共享平台就必须考虑库存合理调配、装备维护周期、租期冲突检测这些维度。我在设计的时候把装备状态拆成了“在库可租”“已被预订”“租赁中”“维修中”“已下架”五种这样运营人员一眼就能看出装备的实时状况也便于后续做装备使用寿命统计。1.2 角色划分与核心业务流程这个系统的角色非常清晰一共三类普通用户检索装备、查看详情、下单租赁、在线支付押金和租金、预约取货时间、发起归还、评价装备。平台运营/管理员装备的上下架、库存调整、审核订单、处理押金退还或自动退还、处理异常订单如逾期未还、装备损坏、查看经营数据报表。系统后台任务无界面角色处理支付回调、超时自动取消未支付订单、逾期订单自动计算违约金。核心业务流程是最关键的用户选了装备后先看预约日历确定可用时间段然后提交订单并同时支付租金和押金支付成功后订单状态变为“待取货”用户在约定时间到线下取货点或约定地点领取装备系统将装备状态置为“租赁中”到达应还日期前用户发起归还运营验货通过后系统自动退还押金订单流转则为“已完成”。如果超过应还日期未归还系统自动进入逾期状态并按规则计算违约金。这整个循环里最容易被忽视的是订单状态机设计。如果状态流转没有设计清楚后面开发时会发现“改了一个状态影响了好几个功能”最后代码里全是if else判断非常容易出bug。我最后的设计是待支付 - 已支付待取货 - 租赁中 - 待归还校验 - 已完成从租赁中和待归还校验可分支到“逾期中”逾期并归还后进入“已完成含违约金”状态用户可在“待支付”状态取消管理员可在任意非终态状态下强制取消如装备损坏、不可抗力等。2. 技术选型与架构设计2.1 为什么是SpringBoot而不是其他方案很多人在选技术栈时会犹豫用Spring Boot还是直接用Spring MVC传统项目用SSM还是Spring Boot我的建议很直接毕业设计阶段SpringBoot是第一选择没有之一。原因也很实际SpringBoot通过自动配置大幅减少了配置文件的编写量。SSM项目写一堆XML配置的时间在SpringBoot里只需要在application.yml里写几行就搞定相当于把精力释放出来专注业务逻辑而不是纠结数据源Bean怎么配。选Java生态还有个非常现实的原因毕业设计答辩时老师非常看重“规范性和工程化”Java的强类型特性加上Spring生态的标准分层模式代码结构天然清晰。分层架构Controller-Service-Mapper让代码的可读性远超脚本语言写的系统在答辩演示的时候也更容易讲清楚每个模块的职责。前端我选了Vue3 Element Plus没有用传统的Thymeleaf模板渲染。原因是现在的前后端分离开发方式更贴近实际企业项目而且Element Plus的表单和表格组件非常适合后台管理页面这类CRUD密集的场景能极大减少前端工作量。2.2 整体架构与技术清单系统整体采用前后端分离架构后端唯一入口是SpringBoot应用通过RESTful API提供数据服务。部署上虽然是一个单体应用但在代码层面严格按模块划分方便后续拆分为微服务。后端核心依赖清单组件版本选型用途说明Spring Boot2.7.x基础框架Spring MVC随Boot版本提供REST接口MyBatis-Plus3.5.xORM与通用Mapper减少SQL编写量MySQL8.0业务数据持久化Redis5.0缓存热门装备、防重复提交、分布式锁Sa-Token1.34登录认证与权限控制MinIO本地部署存储装备图片支付宝沙箱SDK3.7.x模拟在线支付选择MyBatis-Plus而不是纯MyBatis一是逆向工程生成代码非常快二是它自带的条件构造器让动态查询变得极其方便比如装备列表的一堆筛选条件用LambdaQueryWrapper几行就能拼出来。选Sa-Token而不是Spring Security是因为Sa-Token的API设计比Security直观太多登录、鉴权、踢人下线都是一行代码的事对SpringSecurity那套复杂的过滤器链不熟悉的同学来说上手成本低得多。Redis在这个系统里承担了三类工作一是缓存热点装备列表和装备详情降低数据库压力二是实现分布式锁解决并发场景下装备超卖问题三是存储限流计数器和订单防重令牌。2.3 接口设计与统一响应规范前后端分离项目接口规范决定了联调效率。我采用了统一的响应结构code、message、data。code为200表示成功400表示参数错误401表示未授权500表示服务器异常。前端封装了统一的axios拦截器对code做统一处理比如401自动跳转登录页500统一弹错误提示。分页参数统一用pageNum和pageSize返回结构用分页对象包装总记录数和当前页数据列表。时间类型统一用时间戳或标准字符串格式前端不自己去算时区避免出现时间显示错乱。值得一提的一个细节是金额字段统一以“分”为单位存储使用整数类型避免浮点数精度问题。这个在计算租金、押金、违约金的时候非常关键稍后会在计费模块详细解释。3. 数据库设计与核心表结构3.1 用户与信用体系设计用户表算是整个系统的基础但它又不只是简单的账号密码表。在设计时我加入了信用分字段初始默认100分完成订单且无不良记录每次加1分有逾期记录每次扣5分。信用分低于一定阈值比如60分的用户下单时需额外缴纳更高比例的押金甚至限制其租赁高价值装备。这种设计虽然初期实现起来很简单但这为平台后续的风控体系提供了基础数据。用户表核心字段id、username、passwordBCrypt加密存储、phone、avatar、roleUSER/ADMIN、credit_score、status正常/禁用、created_at。密码加密这一点值得多说一句千万不要明文存储密码也不要只用MD5直接上手BCryptSpringSecurity自带这个加密器随机盐机制让相同密码加密后的字符串完全不同安全性可靠很多。3.2 装备分类与库存模型装备表是系统第二个核心表。我把它设计成“装备分类表 装备基础信息表 装备实例表”三层结构但考虑到毕业设计的复杂度没有做太重实际操作拆成两张表装备分类表equipment_categoryid、parent_id、name、sort_order。支持二级分类比如户外帐篷 - 双人帐篷/家庭帐篷野餐用具 - 烤炉/保温箱。装备表equipmentid、category_id、name、brand、specs如“3-4人自动速开帐篷”、description、daily_price每日租金分、deposit_amount押金分、image_url、total_stock总库存、available_stock当前可租库存、rental_count累计租出次数、status。这里有一个经验total_stock和available_stock必须分开存。total_stock代表装备的总量是永不变的基础数据available_stock是动态可租数量每次下单和归还都会变化。如果不区分运营调整装备数量和当前可租状态会混在一起容易出问题。还有一个容易踩坑的点同一件装备在某个时间段内可能已经被预定了。比如系统里总共有5顶帐篷当前可租显示5但其中3顶在五一假期已经被预订实际假期期间只可租2顶。所以只靠available_stock一个字段是没法准确回答“某天能不能租”这个问题的。我在系统中增加了一张equipment_inventory表专门做时间段的库存预占统计以日期为粒度记录每天每种装备的剩余可租数量下单时同步扣减当天库存归还后释放对应日期库存。3.3 订单与租赁状态机订单表是整个系统最复杂的表。核心字段id、order_no唯一订单号、user_id、equipment_id、start_date开始日期、end_date结束日期、rental_days、unit_price、rental_amount租金总额、deposit_amount押金、total_amount实付总额、late_fee违约金、status、pay_time、receive_time、return_time、created_at。rental_days的计算规则为“结束日期减开始日期”。这里要注意一个业务细节租一天和租两天的边界需要约定清楚。比如用户选择“5月1日取货、5月2日归还”算1天还是2天我给出的规则是按自然日计算取货当天算第一天归还当天不算。即5月1日取、5月2日还为1天5月1日取、5月3日还为2天。这个规则虽然简单但必须在用户下单页明确提示否则极易产生纠纷。订单状态机上我前面提到总共有七个状态待支付、已支付待取货、租赁中、待归还校验、已完成、逾期中、已取消。在代码实现层面我推荐用状态值常量定义而不是魔法数字比如public interface OrderStatus { int PENDING_PAY 0; // 待支付 int PAID 1; // 已支付待取货 int RENTING 2; // 租赁中 int PENDING_RETURN 3; // 待归还校验 int FINISHED 4; // 已完成 int OVERDUE 5; // 逾期中 int CANCELED 6; // 已取消 }状态流转不是所有方向都开放我在Service层封装了每个状态变更的方法比如payOrder只能由PENDING_PAY转向PAIDreceiveEquipment只能由PAID转向RENTING这样避免了业务层到处乱改状态导致状态错乱。3.4 辅助表评价、收藏与通知评价表比较简单id、order_id、user_id、equipment_id、rating1-5星、content、images、created_at。这里要注意订单和评价的对应关系应该是“唯一”的一个订单只能评价一次所以order_id要加唯一索引。收藏表id、user_id、equipment_id、created_at联合唯一索引user_id, equipment_id在查询用户收藏列表时可以直接连表查询装备信息。通知表用于给用户发消息比如订单即将到期提醒、逾期通知、押金退还成功通知。这种表的核心是notify_type枚举后续如果要接短信或消息推送直接在发送环节扩展实现即可。4. 关键业务流程与核心代码实现4.1 装备检索、筛选与热装备推荐装备列表页首屏加载必须快。我将热门分类和热租装备做成了Redis缓存key为equipment:hot:list缓存时间设为10分钟。首页加载时先查缓存缓存命中就直接返回未命中再查数据库并回填缓存。查询时支持分类筛选、按日租金区间筛选前端传minPrice和maxPrice、按品牌筛选、按可租状态筛选。列表排序支持三种默认综合排序按rental_count和rating加权、价格从低到高、价格从高到低。综合排序的SQL在数据量不大的情况下直接算即可如果数据量大了可以提前在装备表冗余一个score字段定时任务计算后更新。装备详情页展示核心信息之外还需要展示该装备在未来30天内的“可租日历”。用户点某个日期前端请求后端查询当天库存剩余数大于0则可选。这里用前面说的equipment_inventory表查询某天某装备的剩余租用数量即可。4.2 下单与并发库存扣减这是全系统最容易出Bug的地方。先说明最基础的方案下单时先判断available_stock是否大于0然后扣减1。看起来没问题但如果两个用户同时下单都用“先查询再更新”的逻辑就会都查到剩余1件然后都执行扣减最后库存变成-1这就是经典的超卖问题。解决超卖常见的方案有三种SQL层面的原子更新、乐观锁版本号、数据库行锁。我选择了最简单可靠的方式直接执行带条件的UPDATE语句。Transactional public void createOrder(OrderCreateDTO dto) { // 1. 预占库存原子操作 int rows equipmentMapper.updateAvailableStock(dto.getEquipmentId(), -1); if (rows 0) { throw new ServiceException(该装备暂时无库存); } // 2. 预占日期库存 LocalDate startDate dto.getStartDate(); LocalDate endDate dto.getEndDate(); for (LocalDate date startDate; date.isBefore(endDate.plusDays(1)); date date.plusDays(1)) { int invRows inventoryMapper.decreaseStock(dto.getEquipmentId(), date); if (invRows 0) { // 日期库存不足回滚 throw new ServiceException(date 该装备已被预订); } } // 3. 创建订单 orderMapper.insert(buildOrder(dto)); }对应的SQLUPDATE equipment SET available_stock available_stock - 1 WHERE id #{equipmentId} AND available_stock 0;UPDATE equipment_inventory SET remaining_stock remaining_stock - 1 WHERE equipment_id #{equipmentId} AND stock_date #{stockDate} AND remaining_stock 0;更新行数为0时就说明库存已不足直接抛异常回滚事务。这种方案的好处是完全没有“先查后改”的时间窗口数据库层面就保证了原子性是并发扣减场景下性价比最高的方案。另一个要注意的是防止重复提交。用户在订单提交页可能手抖点了两次“提交订单”如果不做控制就会出现两笔相同订单。我在前端按钮上做了loading禁用这只能防正常用户更可靠的是在后端加上幂等控制用户在进入下单页时后端生成一个preOrderToken返回给前端提交订单时必须携带该tokenRedis中保存该token带5分钟过期时间后端处理时先删除Redis中的token只有删除成功才允许继续下单流程这样即使重复提交也只能成功一次。4.3 租金与押金的计算逻辑租金计算在表面上看起来是简单乘法每日租金乘以天数。但深入一层就发现问题了用户当天取装备时可能只需要用半天是否要按全天收费装备因天气原因提前归还租金是否减免跨节假日时价格是否需要上浮为了保证系统不过度复杂我采用了阶梯化的固定计费规则普通日按 daily_price 计算金额等于 daily_price * rental_days。周末价周六、周日按 daily_price 的1.2倍计算按月历日期判断不是按周几判断。法定节假日价独立维护一张节假日价格表节假日按 daily_price 的1.5倍。长租优惠租期超过3天含的部分打9折超过7天含的部分打8折。这个阶梯规则在数据库表设计时就直接落成一张租价计算规则配置表运营人员可以在后台调整而不是写死在代码里。租金计算的时候先查配置表拿到当日的加价系数和长租折扣再按日期逐日累加。价格单位统一用“分”来存储和计算主要是避免Java浮点数运算导致精度丢失。比如0.1元加0.2元在float计算中会出现0.30000000000000004这种结果用整数分就不会有这个问题。前端展示时再除以100转成元。押金策略我分了三个档次普通用户押金为装备押金标准额的100%信用分在130分以上的优质用户押金减免到50%信用分低于60分的用户押金上浮到150%。这个策略为后续信用体系功能留了很大的扩展空间。4.4 支付流程与押金退还支付模块采用了支付宝沙箱SDK模拟真实支付流程。核心要点在于支付结果必须通过异步回调通知服务器而不是依赖前端跳转同步返回。前端同步返回只做用户提示用服务端以支付宝异步回调为准。我设计支付回调的处理逻辑如下PostMapping(/api/pay/notify) public String payNotify(RequestParam MapString, String params) { // 1. 验签 boolean signVerified AlipaySignature.rsaCheckV1(params, config.getPublicKey(), utf-8, RSA2); if (!signVerified) { return failure; } // 2. 获取业务订单号查询本地订单 String orderNo params.get(out_trade_no); String tradeStatus params.get(trade_status); Order order orderMapper.selectByOrderNo(orderNo); if (order null) return failure; // 3. 判断订单当前状态防止重复回调 if (order.getStatus() ! OrderStatus.PENDING_PAY) { return success; // 已处理过 } // 4. 处理支付成功 if (TRADE_SUCCESS.equals(tradeStatus) || TRADE_FINISHED.equals(tradeStatus)) { orderService.processPaySuccess(order.getId()); } return success; }这段代码里最关键的就是第3步状态判断。支付宝可能会因为网络重试而多次发送同一个异步通知如果没有幂等判断同一个支付结果会被处理两次订单状态会被覆盖用户的库存会被重复扣减。必须用order自身状态作为幂等标记只有“待支付”状态的订单才允许处理支付成功回调。押金退还是在归还验货通过后触发的。归还时运营人员录入装备损耗情况如果装备完好系统立即发起原路退回操作。退款同样使用支付宝统一退款接口退还成功后把订单状态置为“已完成”。需要说明的是如果使用沙箱环境测试退款通常即时到账但在真实环境会有T0或T1的延迟界面提示文案需要考虑这一点不要写成“押金已到账”写成“押金退款处理中”更稳妥。4.5 归还、验货与逾期处理归还环节是租赁业务线上线下的衔接点。我设计的线下流程是用户将装备带到取货点运营人员打开订单详情逐项核验装备数量、外观、附件是否齐全然后在系统中点击“确认归还”系统根据当前时间和应还时间判断是否逾期。若逾期自动计算违约金若未逾期则走押金原路退回的流程。逾期费计算规则如下逾期在24小时内收取当日租金的50%作为违约金超过24小时按日全额累加上限不超过押金金额的2倍。这个上限设计主要是为了避免极端场景下产生天价违约金在用户协议中也需要写明。定时任务每天凌晨2点扫描所有“租赁中”且应还日期小于当前日期的订单将其状态改为“逾期中”并生成一条站内通知提醒用户尽快归还。这个扫描逻辑用Spring的Scheduled注解实现整体非常简单Scheduled(cron 0 0 2 * * ?) public void handleOverdueOrders() { ListOrder overdueOrders orderMapper.selectRentingOrdersByEndDateBefore(new Date()); for (Order order : overdueOrders) { order.setStatus(OrderStatus.OVERDUE); orderMapper.updateById(order); notifyService.sendOverdueNotice(order); } }需要注意的是定时任务的扫描频率如果一天只跑一次用户在凌晨刚过应还时间到凌晨2点之间不会立刻收到逾期提醒。如果想体验更好可以把频率调为每小时一次但这会带来一定的数据库压力折中方案是每天跑两次凌晨2点和下午2点。实际项目中根据订单量灵活调整即可。4.6 管理员端与经营数据报表管理员端的功能看似只是装备和订单的管理实际还包含一个经常被忽略的部分经营数据报表。我做了三个核心统计页面装备运营报表统计每种装备的总订单量、出租次数、租金收入、损坏率。这让运营人员能直观看到哪些装备是“赚钱主力”哪些装备频繁损坏需要考虑更换供应商。订单趋势图按日统计订单量和营收支持近7天、近30天、近一年切换。这类数据直接查询订单表按日期分组聚合即可数据量不大时不需要引入额外的OLAP工具。用户活跃度统计记录用户的登录次数、下单次数、取消次数对高频取消的可疑用户做标记。这个模块帮助发现“恶意占单”用户——下单后不支付等到快超时再取消反复占用库存影响其他用户。我后来在系统中加入一个限制如果用户30天内取消订单达到5次则限制该用户当天的下单权限。5. 实操过程中的常见坑与解决方案5.1 并发超卖问题这个问题在前面已经提到过解决方案但实际排查过程值得再展开。我第一次测试时用JMeter模拟10个用户同时抢购同一顶帐篷库存5件结果生成了8笔订单available_stock变成了-3。排查时发现代码是典型的“先查询再更新”写法Equipment eq equipmentMapper.selectById(equipmentId); if (eq.getAvailableStock() 0) { equipmentMapper.decreaseStock(equipmentId); }这种写法在并发环境下必挂。原因在于两个线程同时读到stock5都判断大于0都执行扣减但扣减时没有条件限制所以最终结果是4而不是应该的3正确的方案就是前面提到的带条件的原子UPDATE语句更新影响行数为0时直接判定库存不足。更进一步的建议是单机环境下用行锁或原子UPDATE已经足够不要过早引入Redis分布式锁。分布式锁需要处理锁超时、锁释放、重试机制复杂度高很多而且它解决的是“多实例部署”时的并发问题毕业设计单实例部署完全用不到。如果你确实想展示分布式锁的用法可以用Redisson的RLock做一轮演示但核心方案仍然要以数据库行级原子操作兜底。5.2 订单超时未支付自动取消用户在创建订单后迟迟不支付订单会一直占用库存这对其他用户很不公平。我实现了超时自动取消机制创建订单时将订单号加入Redis延迟队列key为order:timeout:{orderNo}value为1TTL设为30分钟。用一个定时任务每30秒扫描一次Redis中过期的key发现过期后检查订单状态如果仍是“待支付”状态则执行取消操作释放预占的库存。更优雅的替代方案是使用RabbitMQ的延迟队列插件但引入MQ会增加部署复杂度。用Redis扫描过期key的方式在订单量不大的场景下完全够用代码简单部署无压力。Scheduled(fixedDelay 30000) public void scanExpiredOrders() { SetString keys redisTemplate.keys(order:timeout:*); if (keys null || keys.isEmpty()) return; for (String key : keys) { if (redisTemplate.getExpire(key) ! null redisTemplate.getExpire(key) 0) { String orderNo key.replace(order:timeout:, ); orderService.cancelOrderIfPending(orderNo); redisTemplate.delete(key); } } }这里有一个非常隐蔽的坑Redis的getExpire返回的是秒数如果过期时间在1秒以内返回的是-1或0的几种不同情况不同版本的Redis客户端对过期key在scan时是否立即删除有不同表现。稳妥的做法是在创建订单时往Redis里写入两个key一个带过期时间一个记录订单号定时任务只负责扫描记录订单号的key集合逐个从Redis中查询是否存在如果不存在了说明已经过期再去查一次订单状态决定是否取消。这样逻辑更清晰也避免了scan过程中key刚好被删除导致判断错误的情况。5.3 跨天计费与租期计算边界日期边界问题非常容易出逻辑漏洞。我最初实现的租金计算是直接计算“结束日期减去开始日期”的天数差然后乘以单价。结果用户选择5月1日取、5月3日还我算出来是2天但用户认为5月1日、2日、3日共用了3天双方认知不一致。后来我在下单页面加了一行很醒目的说明“取货当日计费归还当日不计费共X天”。同时在订单确认接口返回租期明细前端回显出来给用户确认确认后前端和后端都校验一遍。后端在计算时用这样一个工具方法public static int calcRentalDays(LocalDate startDate, LocalDate endDate) { if (endDate null) { throw new BusinessException(归还日期不能为空); } return (int) ChronoUnit.DAYS.between(startDate, endDate); }这个公式算出来是“结束日期和开始日期之间的自然日差距”按“取货日算第一天、归还日不算”的规则刚好就是正确租期。要注意校验结束日期必须晚于开始日期否则会返回负数或0必须在入参时做参数校验。5.4 前端时间显示的时区陷阱前端是Vue3用户选日期用的是日期选择器传给后端的格式是“yyyy-MM-dd”。我把这些日期直接解析为LocalDate后端运算和存储全都用LocalDate避免引入LocalDateTime和Timestamp之间时区转换的问题。但在订单列表展示时创建时间这类字段需要精确到秒我用的是LocalDateTime序列化配置统一为“yyyy-MM-dd HH:mm:ss”并且在后端统一设置为Asia/Shanghai时区。真正踩过的坑是把LocalDateTime序列化传给前端时如果不配置Jackson的JavaTimeModule会直接报错或者序列化成一个对象数组前端拿到一堆数字完全看不懂。解决方案是在application.yml中配置spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai同时为LocalDateTime注册序列化器Bean public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() { return builder - { builder.serializers(new LocalDateTimeSerializer( DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); builder.deserializers(new LocalDateTimeDeserializer( DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); }; }凡是涉及日期时间的展示前后端约定统一走字符串格式不去做时间戳转来转去的操作这是最省心的方案。5.5 图片上传与存储装备图片上传我一开始想做本地磁盘存储但后来发现开发环境和生产环境路径不一致前端访问图片的URL写死非常麻烦。后来换成MinIO做对象存储它在本地部署非常简单支持S3协议图片上传完成后返回一个外链前端直接展示图片URL。权限控制上装备图片是公开可访问的所以用了MinIO的只读策略上传走后端临时凭证这样用户没法直接篡改图片。为什么不用阿里云OSS主要是沙箱环境下不想额外申请云资源MinIO部署在本地既能完整模拟对象存储的流程又不用花钱对于毕业设计来说完全够用了。5.6 安全与权限控制登录认证我用了Sa-Token登录成功后在Redis中保存一个token后续请求通过token获取用户信息。权限上用注解拦截管理员接口加SaCheckRole(ADMIN)普通用户接口加SaCheckLogin()。这个方案对前后端分离项目非常友好前端每次请求在拦截器中带上token后端在SaTokenContext中默认解析。需要特别注意的是防止水平越权用户在查看订单详情、取消订单、评价订单时必须校验订单的user_id是否等于当前登录用户ID。这个细节容易被忽略比如用户A通过修改订单ID参数查看订单B的信息如果后端不校验归属就是严重的安全漏洞。我在所有涉及用户私有数据的接口上都加了归属校验统一注解或工具方法例如private void checkOrderOwner(Order order, Long currentUserId) { if (!order.getUserId().equals(currentUserId)) { throw new ServiceException(无权操作该订单); } }其实安全这块内容还可以做很多比如接口限流防刷、防止恶意下单、参数校验等但毕业设计做到这一步已经能体现出一名开发者对系统安全性的基本认知了。如果还有富余时间建议把接口限流加上用Redis做一个简单的滑动窗口计数器即可。6. 可扩展方向与个人建议这个系统做完跑通之后可以扩展的方向其实很多。从技术上来说可以增加消息队列订单事件、支付事件解耦、分库分表订单表量级上来、容器化部署Docker Compose一键启动从业务上来说可以增加线下门店自提点的地图展示、装备保养周期的自动提醒、用户信用分与租赁押金动态关联、基于用户画像的装备推荐、社区内容露营攻略和装备评测等。其中我认为最有价值的是两个方向一个是“共享”概念深化即用户可以将自有闲置露营装备托管到平台出租平台抽取佣金。这意味着系统需要增加“装备所有者”这个角色以及收益结算功能对表结构的影响较大但对毕业设计的完整性和创新性加分很多。另一个是增加物联网方向的概念设计比如在帐篷或贵重装备上绑定二维码或智能锁租赁开始时由系统下发解锁码归还后锁自动失效。这种设计虽然实现起来成本高但在答辩时作为展望部分会让人眼前一亮。如果让我重新做一遍这个项目我会把重心放在两个地方一是测试尤其是并发和订单状态流转的自动化测试把每个状态转换的边界条件都写成单元测试和接口测试避免“一个功能改完另一个功能挂了”的连锁问题二是前端表格和表单交互的打磨管理员后台的表格筛选、排序、批量操作这些功能看似简单实际工作量往往被低估。说了这么多我在这个项目里收获最大的其实不是某个技术点的掌握而是建立起了一套业务思维从用户下单到归还每一步都有状态流转每一步都要考虑数据的一致性、金额的准确性、操作的边界条件这种全链路思考的习惯是写几个demo练不出来的。如果你正在做类似的系统建议早期把状态机图画完整把计费规则和用户确认流程想清楚后面的开发会顺畅很多。