如果你正打算做一个SpringBoot相关的全栈项目,又不想落俗套地做个图书管理、商品秒杀,那农机租赁服务平台是个挺不错的选择。这个项目本质上是把共享经济那一套逻辑搬到农业场景里:农户家里有闲置的拖拉机、收割机、旋耕机,与其放在院子里吃灰,不如挂到平台上按天出租给周边需要作业的农户;另一边,种粮大户或者农机手有作业需求,也不用非得自己花几十万买设备,直接按需租就行。这个平台做出来之后,既覆盖了信息发布、检索、下单、支付、订单履约这些电商核心链路,又额外带上了农机认证、作业计价、押金结算、定位检索这些农机特有的业务逻辑,非常适合拿来练手SpringBoot、MyBatis-Plus、Vue这一整套前后端分离技术,也能让毕设或者简历项目有足够的业务复杂度。
这篇文章我打算从项目定位、技术选型、数据库设计、关键业务实现、部署避坑这五个方面展开,不整虚的,全部按真实项目开发时会遇到的问题来写。你可以把它当作一份完整的项目复盘来读,也可以直接照着里面的表结构和业务方案去动手实现。
1. 项目定位与技术选型
1.1 农机租赁到底在解决什么场景问题
先搞清楚平台解决的核心问题是什么。农业机械化率越来越高,但农机价格一直居高不下,一台大型联合收割机动辄二三十万,普通农户根本不会为了每年那十几天的收获季去买一台。与此同时,很多农机手和农机合作社的设备在非作业季长期闲置,闲着就是亏钱,因为农机折旧是按年算的,不是按作业小时算的。
农机租赁平台要做的就是两端撮合:一端是有农机资源的供给方,把农机信息、可作业区域、计费方式发布上来;另一端是有作业需求的需求方,按区域、按农机类型、按作业季节去搜索并线上下单。这个业务模型和传统的房屋短租很像,但农机本身是移动的作业设备,订单不只是“租你一台机器”,还涉及作业地点、作业亩数、到达费用、机手作业费这些农业特有的规则。
站在技术实现的角度,这个项目不只是一个后管系统加一个H5商城那么简单,它至少要覆盖四个端:农户/农机手使用的C端小程序或H5、管理员使用的后台管理端、农机主使用的设备发布端,以及后端管理服务。如果你作为毕设去做,一般会把前两个端合并成一个Vue管理端,再加一个面向用户的H5端节省工作量。
1.2 为什么后端选了SpringBoot而不是其他方案
网上关于SpringBoot的介绍铺天盖地,但真正到自己做项目选型时,还是要说清楚理由。SpringBoot最大的价值是解决了Spring时代配置地狱的问题——以前搞一个SSM项目,要写一堆XML配置,数据源、事务管理器、Mapper扫描、拦截器全部要靠手配,配错一个Bean启动就报错。SpringBoot把这些统一变成了自动配置,内置Tomcat,打成一个Jar包就能跑,这对独立开发和中小型系统来说体验好得不是一星半点。
拿这个农机租赁平台来说,它需要的核心能力SpringBoot全都覆盖得比较完善:
- Web层用SpringMVC,天然支持RESTful接口设计,C端和后台可以共用一套API;
- 持久层整合MyBatis-Plus,CRUD不需要手写SQL,业务复杂时再上自定义SQL和分页插件;
- 安全认证可以用Spring Security或者更轻量的Sa-Token,JWT无状态登录对前后端分离非常友好;
- 定时任务用Spring自带的@Scheduled或者集成Quartz,正好用来处理超时未支付订单、作业完成后的自动结算;
- 缓存整合Redis,会话、热点农机数据、验证码这些都能放进去。
相比之下,PHP的LNMP方案更适合快速搭CMS系统,Python的Django在ORM和Admin后台方面虽然省事,但招人、部署、后续维护和SpringBoot技术栈的成熟度、社区解决方案丰富度比还是有差距。所以做这个项目,SpringBoot几乎是标准答案,简历上写它也能更好地面试。
1.3 前端、数据库和中间件怎么搭配
整个系统的常用户口和业务承载,我是这样搭配的:
- 前端框架选Vue3加Element-Plus,原因很简单——后台管理界面的主流方案,组件成熟,文档齐全。用户端如果不想单独做小程序,可以直接用Vue3写一个移动端适配的H5页面。维表数据和后台图表类的展示用ECharts,看农机分布和订单趋势比较直观。
- 数据库用MySQL 8.x,字符集一定要设置成utf8mb4,农机描述里经常有表情符号或者生僻字,常规utf8会直接报错或者乱码。
- Redis负责缓存、分布式锁和临时验证码,另外就是做农机热力榜单这类实时性要求高的缓存数据。
- 文件存储这块,初期不用想得太复杂,本地磁盘加FastDFS或者直接上MinIO就行,农机合格证、行驶证照片、农机实拍图、作业凭证这些图片资源必须要有一个统一的对象存储入口,不要散落在业务代码里。
- 后端项目用Maven管理依赖,SpringBoot版本这里要重点说一句——不要一上来就用3.x和4.x的早期或者过新版本,JDK版本、依赖兼容性都会出问题。我用的是SpringBoot 2.7.18配JDK8,这是我目前做这类项目比较稳妥的组合,网上资料多,遇到问题也容易搜到答案。如果你是非要用JDK17的,SpringBoot选3.x系列,但注意旧版MyBatis-Plus相关插件要升级到对应版本。
1.4 项目工程结构怎么规划
很多新手拿到项目就开写,代码全堆在一个Application类或者一个Controller包里,后面扩展一个功能要翻半天代码。这个项目我建议后端代码按这样的结构划分:
com.farm.lease ├── common // 公共模块:统一返回结果、异常处理、常量、工具类 │ ├── result │ ├── exception │ └── utils ├── config // 配置类:RedisConfig、WebMvcConfig、MybatisPlusConfig、Knife4jConfig ├── controller // 接口层:按业务模块划分,只做参数接收和返回 ├── service // 业务层:事务控制、核心业务逻辑 │ ├── impl │ └── task // 定时任务 ├── mapper // 数据访问层:MyBatis-Plus的Mapper接口 ├── entity // 数据库实体映射 ├── dto // 前端交互参数对象 ├── vo // 视图返回对象 └── security // 登录认证、权限控制相关这个结构最大的好处是各层职责清晰,controller只负责接收参数和调service,service里写业务规则,mapper只管数据读写,不会出现Controller里直接写SQL这种后续没法维护的情况。前端项目单独建一个目录,和Vue工程分开,开发时通过代理转发接口,避免跨域问题,部署时再统一打包放到Nginx下。
2. 系统核心模块拆解与数据库设计
2.1 用户与农机认证怎么设计
农机租赁不能像闲鱼卖二手手机那样随便注册就能发布,农机作业涉及人身安全和作业质量,所以平台一定要设计资质审核链路。
用户体系上,我建议设计三类角色:普通用户(需求方)、农机主(供给方)、平台管理员。普通用户注册后可以直接浏览农机、下单租赁;农机主需要额外提交身份证、驾驶证、农机行驶证、农机照片这些材料,后台审核通过后才有权发布农机。管理员负责农机审核、用户管理、订单仲裁、投诉处理。
这个设计在数据库上会体现为user表加一个user_type字段,农机主资质单独放一张farmer_auth表,审核状态用audit_status字段(0待审核、1通过、2驳回)。农机主提审后,后台管理员审核的操作要记录日志,如果被驳回还要填驳回原因,方便农机主修改后重新提交。
2.2 租赁订单的核心流程
整个平台最核心的链路是:搜索农机 → 查看农机详情和计价规则 → 发起租赁下单 → 支付押金或租金 → 农机主接单确认 → 线下作业/取还机 → 确认完成 → 资金结算。
农机不是快递,无法通过物流发货,履约过程是在线下完成的。所以我在订单状态里专门设置了待接单、待作业、作业中、待确认完成、已取消、已完成、已退款这些状态。农机主接单之后,双方可以电话沟通实际作业时间;作业完成后由需求方确认,平台再把冻结的租金打给农机主。这种模式其实和美团到店消费很像,平台做的是信息撮合和资金担保,而不是物流履约。
支付这块建议直接对接微信支付Native支付或者H5支付。如果你是做毕设,不需要真开商户号,可以做一个模拟支付的开关——本地环境走模拟支付,演示时输入验证码就算支付成功,这样功能链路是完整的,又不会卡在商户资质上。这块在论文里也可以如实写:设计了对接微信支付的接口,同时支持沙箱模拟支付用于测试。
2.3 数据库核心表设计
我建表时始终遵循一个原则:核心业务表必须有create_time、update_time、deleted逻辑删除标记,金额字段用decimal(10,2),状态字段用tinyint并注释清楚含义。下面是六张跑不掉的核心表:
用户表(t_user):
- id 主键、open_id(微信OpenID,可空)、phone、password、user_type(1普通用户 2农机主 3管理员)、nick_name、avatar、real_name、id_card、status
农机信息表(t_machine):
- id 主键、user_id(所属农机主)、machine_name、category(拖拉机/收割机/旋耕机/播种机/植保无人机等)、brand、model、manufacture_date、hours_used(已使用小时)、attachment_urls(图片JSON数组)、province/city/district、address_detail、longitude、latitude、rent_type(1按天 2按亩 3按小时)、rent_price、deposit(押金)、description、audit_status、status(1上架 2下架)
租赁订单表(t_lease_order):
- id 主键、order_no(唯一订单号)、machine_id、lessee_id(需求方)、lessor_id(农机主)、total_amount、deposit_amount、pay_amount、status(0待支付 1待接单 2待作业 3作业中 4待确认 5已完成 6已取消 7退款中)、start_time、end_time、work_province/work_city/work_district、work_address、acreage(作业亩数)、contact_name、contact_phone、cancel_reason、finish_time
支付流水表(t_payment_record):
- id 主键、order_id、order_no、pay_type(1微信 2模拟支付)、pay_amount、transaction_id(第三方流水号)、status、notify_time、notify_data(回调原始报文,这个字段很重要)
评价表(t_evaluation):
- id、order_id、machine_id、user_id、rated_user_id、score、content、images
意见反馈与投诉表(t_feedback):
- id、user_id、type、content、images、status、handle_result
农机作业日历可以暂时不做成表,直接在订单表上通过时间条件查询判断某台农机在指定时间段内是否已被租赁。如果农机主可能有多台机器,每台机器的档期独立,这样查询不会串。
2.4 订单状态机与流转约束
我见过很多订单系统写着写着就乱了的,根本原因是状态更新没有单一入口,业务代码里到处直接order.setStatus(),最后都不知道这个状态是什么时候变的。做这个项目时,我建议把状态的流转控制收敛到OrderService里的一个统一方法,并且明确好合法的流转方向:
状态流转图可以这样理解:
- 待支付(0)只能走向待接单(1)或已取消(6),取消条件是有时限的,一般15分钟或30分钟不支付就自动取消;
- 待接单(1)只能走向待作业(2)或已取消(6),农机主接单后不能随意取消,如果确需取消,要让农机主输入原因并经过平台审核,防止恶意拒单;
- 待作业(2)在作业开始后走向作业中(3),这个操作可以由农机主或者任意一方确认;
- 作业中(3)结束后走向待确认(4),需求方确认后走向已完成(5);
- 已完成(5)之后只能走评价流程和售后投诉流程,不能再改金额。
这里面有一个点常常被忽略:无论哪一步,数据库里的订单记录都不能只更新status字段,而要考虑把操作人和操作时间一并更新,也就是记录status_history。我会规划一张订单状态履历表,每个状态流转都插一条数据,后面出纠纷时拿着时间线直接就能对账。这个设计在答辩时也是一个加分项。
3. 关键业务实现与踩坑记录
3.1 农机检索中的位置距离与条件筛选
农机租赁平台有一个很有业务特色的查询场景——按位置找附近的农机。农户要找收割机,他关心的是这台收割机到他的田里有多远,太远了光拖运费就不划算。
在数据量不大(几千台)的情况下,不需要引入Elasticsearch或者专门的GIS数据库,直接在MySQL里用经纬度计算距离即可。农机表里有longitude和latitude字段,查询时用Haversine公式计算距离,SQL这样写:
SELECT id, machine_name, rent_price, ROUND(6371 * 2 * ASIN(SQRT( POWER(SIN((#{lat} - latitude) * PI() / 180 / 2), 2) + COS(#{lat} * PI() / 180) * COS(latitude * PI() / 180) * POWER(SIN((#{lng} - longitude) * PI() / 180 / 2), 2) )), 2) AS distance_km FROM t_machine WHERE audit_status = 1 AND status = 1 HAVING distance_km < #{distance} ORDER BY distance_km如果你的表数据量超过几十万,这条路效率就不够了,需要上Elasticsearch的geo_distance查询,或者用MongoDB的GeoJSON。但对毕设或者中小型平台,上面的SQL配合联合索引完全够用。注意不要在where里直接用距离去算,因为那会让索引失效,应该先按经纬度范围粗筛(比如纬度加减0.5度、经度加减系数),再在HAVING里精确过滤,性能会更稳。
筛选条件上,除了距离,还要支持农机类型(下拉菜单)、租金价格区间、租用方式(按天/按亩/按小时)、农机主信用分。这些条件在Mapper里用MyBatis-Plus的LambdaQueryWrapper动态拼装就够用,不需要写一堆XML。
3.2 支付回调与订单状态的一致性保证
支付是这类平台最容易出事故的环节。微信支付的工作方式是:用户在小程序里发起支付,微信那边收到钱之后异步通知你的后端接口,你的后端收到回调后要校验签名、校验金额,然后更新订单状态、给农机主发通知。这个异步通知的时机是不可控的,可能支付成功了但通知一直没到或者延迟很久,也可能通知重复到达好几次。
我在做这个模块时,吃过两个亏,在这里直接告诉你:
第一个是回调用“参数校验”。回调接口收到微信的通知后,不能只验签名就完事,还要拿通知里的amount.total和本地订单的pay_amount做比对,不一致的必须拒绝,防止中间环节报文被篡改或回调地址被恶意刷。验签失败的回调不返回成功应答,微信那边会重试,这没问题。
第二个是重复回调导致的状态覆盖。微信同一个支付结果会通知多次,后端的处理逻辑必须是幂等的。我当时的做法是:先用order_no加redis分布式锁,锁内再查一次订单状态,如果已经是“待接单”或者“已支付”,直接返回成功不再处理。为了稳妥,还加了一张payment_record表,每次回调先尝试插入唯一transaction_id,如果插入冲突说明这单处理过,直接跳过。这套方案下,就算通知来了十次,也只有第一次真正改状态。
3.3 超时订单自动取消的定时任务逻辑
用户下单后如果一直不支付,订单要自动取消,农机这段时间才能重新被其他用户看到。我一开始用一个SpringBoot的@Scheduled定时任务,每30秒扫描一次订单表,把超过15分钟还没支付的订单批量取消。逻辑很简单,生产上也确实能跑,但数据量大了之后会有两个问题:
第一个是扫描范围太宽,每次都全表扫,数据库压力大。优化方案是扫的时候只关注最近30分钟内创建且status=0的订单,配合create_time索引,每次只扫小范围。
第二个问题是批量更新的时候,要小心并发,不能把用户正在支付的订单给取消了。用户在最后几秒完成支付,此时后台扫到了这笔订单超时,两边同时操作就会出问题。我的做法是更新语句里带着状态条件:
UPDATE t_lease_order SET status = 6, cancel_reason = '超时未支付自动取消', update_time = NOW() WHERE id = #{orderId} AND status = 0如果影响行数是0,说明订单已经被人改了状态,就不能再取消。这个“状态条件更新”的思路在并发场景下非常重要,它其实就是一个轻量的乐观锁。类似的做法在农机主接单、需求方确认完成时也都应该用上。
3.4 农机档期冲突与防重复下单
还有一块新手很容易翻车:用户给同一台农机下单,A客户选了8月20日到8月25日,B客户在A还没支付的时候也选了同样的日期并发起下单。如果没有防护,两个订单都创建成功了,等于超卖。
结合前面说的状态机,最稳妥的方案是在订单创建时做一次档期冲突校验,查询条件就是:同一台机器、状态是待支付/待接单/待作业/作业中/待确认(也就是说订单还在生效流程中,已取消和已完成的除外),并且时间区间有交集:
SELECT COUNT(*) FROM t_lease_order WHERE machine_id = #{machineId} AND status IN (0,1,2,3,4) AND start_time < #{requestEndTime} AND end_time > #{requestStartTime}如果这个count大于0,直接返回“该农机在所选时间段已被租赁”,下单不成立。这个同样要用唯一索引保障,防止查询后插入时并发穿透——如果数据库用的是MySQL,可以在订单表上建一个(machine_id, start_time, end_time)的业务索引来做最后底线。更硬核的方案是引入Redis分布式锁,但做这个平台时,上面的两段式校验加数据库兜底已经很够用了。
3.5 计费规则与押金结算的设计细节
农机租赁的计费五花八门:按天租、按亩作业、按时租,还有的要算拖运费。我建议不要把这些规则写死在代码里,而是做成计价规则表(t_price_rule),关联到农机的category和单台农机上。比如一台收割机可以同时配置“按亩作业8元/亩,最低起步20亩”和“按天出租1500元/天,含机手食宿自理”两种方案,用户在详情页看到的是组合后的报价,下单时选择一种方案。
押金处理也需要独立设计。押金不等于租金,它在订单完成后应该自动退回到用户余额或者原路退回。我设计的是:用户下单时支付租金+押金,订单进入待作业状态后押金冻结;作业完成且双方无异议后,平台把租金部分结算给农机主,押金原路退回给用户。要是作业过程中农机损坏或者没有按时到位,需求方可以发起平台介入仲裁,仲裁结果出来前押金一直冻结。这套逻辑在做的时候会踩到很多边界情况,比如部分退款、退款到余额而不是原路支付,处理好这些细节,项目的完成度会比普通毕设高出来一大截。
4. 工程化落地与部署调试
4.1 SpringBoot中MyBatis-Plus的实用配置
MyBatis-Plus是这个项目在数据访问层的主力,我在实际项目里一般这样配置:
mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml type-aliases-package: com.farm.lease.entity configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 id-type: assign_id这里要重点说两个配置:第一个是map-underscore-to-camel-case,数据库字段是create_time,Java实体类字段是createTime,如果没开这个配置,查询结果会全部为null,很隐蔽。第二个是logic-delete-field,全表逻辑删除,查询时会自动加上deleted=0的过滤条件,应用层不用每次手动写。
分页查询直接用MyBatis-Plus的分页插件,核心是配置一个拦截器:
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }没有这个插件的话,你调Page也会查出全表数据,这个坑很经典。另外注意,如果你把Mapper XML和注解SQL混用,接口方法上的注解SQL优先级更高,XML里同名方法的SQL会被忽略,调试的时候不要在这上面浪费时间。
4.2 认证与接口权限设计
农机租赁平台至少有三类角色的权限区分,普通用户能看到农机详情并下单,农机主能管理自己的农机和订单,管理员能审核农机和处理投诉。我用Sa-Token做登录认证,比Spring Security上手快得多,自带的注解权限校验也很方便。
登录流程是:用户微信授权登录或手机号+验证码登录,后端生成token返回前端,前端每次请求在header里带token,后端通过拦截器统一解析并存入当前用户上下文。每个核心接口都要做两层校验:第一层校验是否登录,第二层校验是否有操作权限。比如订单详情接口,需求方和农机主都能看,但普通用户不能看别人的订单,更不能取消别人的订单。
具体实现上,Controller里的当前用户可以通过Sa-Token的StpUtil.getLoginId()获取,然后和请求参数里的userId比对,不一致就抛出业务异常。千万不要直接把前端传的userId拿来当操作用户,那是很经典的水平越权漏洞。网上那些“越权攻击”渗透测试,打得最多的就是这种接口。面试如果问到这块,你能把这个讲清楚,加分是实实在在的。
4.3 打包部署与Vue前端整合
开发阶段前后端分离,但部署时可以有两种选择。第一种是前后端分开部署,后端打Jar包跑在服务器上,前端构建成静态文件放到Nginx里,Nginx再做/api反向代理。第二种是直接把前端构建出来的dist目录复制到SpringBoot的resources/static下,打成一个大Jar包一键启动。毕设演示和写论文时第二种方式方便很多,不需要额外安Nginx。
如果说要把前端打包进SpringBoot,注意清理旧的静态资源,Maven clean package时不要缓存了旧的dist目录。另外后端接口的上下文路径建议统一加/api前缀,比如/api/machine/page,这样不管是开发环境的Vite代理还是生产环境的Nginx转发,只配一个前缀规则就完事。
Vite开发环境联调,在vite.config.js里这样配代理:
server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }后端接口明确返回统一结果格式,我用的是Result对象,字段包含code、message、data。code为200表示成功,其他为失败。前端axios响应拦截器里统一处理错误提示,不要在每一个页面都写一遍弹窗逻辑。
4.4 数据库连接池和插件版本选择
数据库连接池我就用HikariCP,SpringBoot 2.x默认内置的就是它,配置很小的几个参数就够了:
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/farm_lease?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000这里有个小细节:连接串里的serverTimezone一定不能省,否则在高版本MySQL驱动下会报时区错误。allowPublicKeyRetrieval=true是MySQL 8.x驱动在连接时需要的,否则可能出现Public Key Retrieval is not allowed。
插件和版本选择上,我再提醒一遍:SpringBoot 2.7.x对应MyBatis-Plus 3.5.x,knife4j接口文档对应4.x版本,Redis客户端用commons-pool2,这些版本在Maven中央仓库里都有明确的兼容声明,先到官方GitHub的README看一眼再引入,能省去至少一天的依赖冲突排错。
5. 常见问题排查与避坑实录
5.1 SpringBoot版本太高引发的连锁问题
这个问题在热搜里能排得上号,太真实了。很多新手打开Spring Initializr,默认下载的就是SpringBoot 3.4.x甚至更高版本,然后引入旧版MyBatis-Plus Starter,启动直接报错Invalid value type for attribute 'factoryBeanObjectType'。原因很简单,SpringBoot 3.x基于JDK17和Jakarta EE规范,旧的MyBatis-Plus还没有适配。
我的建议是:如果你是做部署演示和写论文,不要追求版本最新。SpringBoot 2.7.18加MyBatis-Plus 3.5.7加JDK8是目前兼容性最稳的组合。如果你确实需要JDK17,那就整个技术栈一起升级,SpringBoot换到3.2.x,对应升级MyBatis-Plus 3.5.9以上,还要注意javax.servlet替换成jakarta.servlet。这个决策在项目一开始就要定好,不然后面每引入一个依赖都在踩版本坑。
5.2 接口联调遇到跨域和参数接收问题
前后端分离开发时,跨域问题经常遇到。如果前端和后端都在本地,最简单的方案是在后端加一个全局跨域配置:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }但注意,如果用了allowCredentials(true),allowedOriginPatterns不能用*,必须写具体域名,这是浏览器规范限制。如果后端还配置了拦截器,拦截器里要放行OPTIONS请求,否则前端预检请求直接就被拦截了,表现就是明明接口能通但浏览器一直报跨域。
参数接收这块,@RequestBody接收JSON对象和@RequestParam接收表单参数要分清楚。前端axios默认POST的是JSON,后端接口要用@RequestBody;如果你是页面表单提交,要用@RequestParam或者直接让方法参数名匹配表单字段。最常见的问题是后端用@RequestBody接收,前端用form-data方式提交,后端一直拿到null。
5.3 部署上线时的端口、日志和静态资源问题
本地开发一切正常,部署到服务器就出问题,这类问题多半集中在三个地方。
第一个是端口问题,Linux服务器上防火墙没有放行8080端口,外网访问不到。检查的时候用curl localhost:8080看本机是否通,如果本机通但外网不通,优先查安全组和防火墙。
第二个是日志里看不到有效的错误信息。提前把logback配置好,至少要做到生产环境输出到文件、并按天滚动保留30天。排错第一件事就是看日志,没有日志寸步难行。
第三个是静态资源404。前端打包后的dist目录放进resources/static后,如果接口返回的图片地址是写死的localhost:8080,在服务器上就全部裂图。我建议所有图片上传后保存相对路径,返回给前端时通过配置项拼完整路径,这样切换环境时只需要改一个配置。
5.4 典型问题速查表
我把这个项目开发中最常见的问题整理成一张速查表,建议你收藏一份,遇到问题先对号入座:
| 问题现象 | 可能原因 | 解决思路 |
|---|---|---|
| 启动失败,提示数据库连接失败 | 数据库服务没启动,或URL、账号密码不对 | 先用Navicat或命令行测试连接,确认无误后再启动应用 |
| 查询结果全是null | MyBatis-Plus没开启驼峰映射 | 检查配置map-underscore-to-camel-case是否为true |
| 分页查询无效,返回全表数据 | 缺少分页插件拦截器 | 注入MybatisPlusInterceptor并添加分页内置处理器 |
| 前端请求报跨域错误 | 后端CORS未配置,或预检请求被拦截器拦截 | 配置全局CORS并放行OPTIONS请求 |
| 商品图片不显示 | 图片返回的是绝对地址localhost | 统一用配置项拼图片访问前缀 |
| 定时任务没有执行 | 漏加@EnableScheduling注解 | 启动类上加上@EnableScheduling |
| 接口报401但token明明有 | header名称前后端不一致 | 前后端约定统一header名称,如Authorization |
| 删除数据后查询还能看到 | 逻辑删除字段类型或配置不对 | 确认logic-delete-field值,实体类上写@TableLogic |
| 支付回调重复处理 | 回调接口不是幂等 | 增加唯一订单号+事务状态校验,或建唯一索引 |
| 依赖冲突启动报错 | SpringBoot版本与Starter不兼容 | 对齐版本号,尽量用Starter自带管理版本 |
这部分内容看起来琐碎,但每一个都是我在实际开发中真实踩过的。你可以把它们写进开发文档或者项目README里,论文的“系统测试”章节也可以作为测试用例的一部分。
做这类SpringBoot全栈项目,其实最忌讳的就是把代码写出来能跑之后就撒手不管。你把它当成一个真正的产品来做,认真设计订单状态机、处理并发、对接回调、考虑部署运维,这些思考过程和实际调通的细节,远比代码本身有价值。我个人的体会是,农机租赁这个业务场景特别适合练SpringBoot技术栈,它既有电商项目的交易属性,又有“线上交易+线下履约”的与众不同的业务复杂性,好好做完并复盘一遍之后,你对SpringBoot生态的理解会明显不一样。答辩或者面试时,不用讲太多虚的,直接把你对支付幂等、定时任务、状态机约束这几个点的设计思路讲清楚,已经可以证明你是一个有工程意识的人了。