1. 项目背景与整体设计思路
1.1 为什么选农业设备租赁这个业务场景
农业设备租赁这个赛道在系统开发里挺特殊的。它不像普通电商那样纯粹是商品买卖,而是融合了商品管理、预约时间窗、订单状态流转、押金结算、乃至设备维护记录等多个业务模块。拿我接到这个项目时的第一反应来说,它的核心难点在于“设备的可用性”和“租赁周期”这两个概念必须被建模清楚,而不是简单写个增删改查就完事。
举个例子,一台收割机在6月10日到6月20日之间被租出去了,那么6月21日之后才能被新的订单预约。如果你只是给设备表加一个“状态”字段,状态改成“已租出”,那么你就没法精确到日期级别的排期,用户想看某台设备明天能不能租,你只能靠人工去翻订单。所以一个农业设备租赁系统,本质上需要把“设备档案”和“租赁排期”拆开考虑,这就天然适合用关系型数据库配合合理表结构来做。
我最终把设备表设计成只存静态属性,比如设备名称、型号、品牌、日租金、押金、所在地区、设备图片,而把“哪天被租走了”交给订单表去管。查询可用设备时,用一条带条件的SQL去排除掉存在时间冲突的订单即可。这就是整个系统里最核心的一块业务逻辑。
1.2 前后端分离架构的落地取舍
选前后端分离架构,我是经过考量的。这个项目本身是一个面向毕业设计、个人作品集以及中小团队快速搭建业务系统场景的典型模板。前后端分离的好处在于,前端可以独立部署在Nginx上,后端用SpringBoot打成Jar包独立运行,两边通过RESTful接口通信,互不干扰。哪怕是部署在同一台服务器上,也可以用不同的端口甚至不同的子路径来隔离,后期做扩容、做小程序端适配,都不需要动后端的代码结构。
具体到技术栈:
- 后端:SpringBoot 2.7.x + MyBatis + MySQL 8.x,JWT做登录鉴权
- 前端:Vue 2 + Vue Router + Axios + Element UI
SpringBoot选2.7.x而不是3.x,主要是考虑到很多国内教程和依赖生态对SpringBoot 2.x的支持最稳,第三方starter和网上资料都更丰富。如果你用3.x,JDK版本需要17起步,如果开发机安装的还是JDK 8,那就会直接遇到一堆版本不兼容的报错。我这个项目的目标是让大多数人能跑起来,不要在一开始的环境配置上劝退。
Vue方面我用的是Vue 2。不是说Vue 3不行,而是Element UI对Vue 2的支持是开箱即用的,组件生态非常成熟,对于这种后台管理系统来说,表格、表单、弹窗、分页这些组件拿过来就能用。Vue 3当然也可以,只是配套的Element Plus在部分API上有些差异,为了项目源码的通用性,选Vue 2更符合“快速复现”这个核心诉求。
1.3 完整功能模块拆解
一个完整的设备租赁系统,我拆成了以下几个功能模块:
- 用户管理:分普通用户和管理员两个角色,使用JWT做登录态管理,密码使用MD5加盐存储。
- 设备管理:设备的添加、编辑、下架、图片上传(本地存储)。
- 租赁下单:选择设备、选择起止日期、系统自动计算租金和押金,生成订单。
- 订单管理:用户查看自己的订单,管理员审核订单、确认归还、处理违约。
- 排期冲突检测:在提交订单时校验所选时间段是否与其他订单冲突。
- 统计看板:管理端展示设备总数、订单数量、营业额等简单统计图表。
这套模块拆出来之后,前后端的接口设计就非常清晰了。每个模块对应一组RESTful接口,前端按页面维度去调用接口,数据流简单直接。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心实现细节与实战踩坑记录
2.1 数据库表结构设计与关键字段说明
数据库我一共建了6张表,分别是:用户表、设备表、订单表、设备分类表、公告表、操作日志表。这里挑几张关键表说说设计思路。
用户表(user)核心字段如下:
sql复制CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码(MD5加盐)',
`real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号',
`role` tinyint(1) DEFAULT '0' COMMENT '角色 0-普通用户 1-管理员',
`status` tinyint(1) DEFAULT '1' COMMENT '状态 1-正常 0-禁用',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
设备表(equipment)核心字段:
sql复制CREATE TABLE `equipment` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL COMMENT '设备名称',
`category_id` int(11) DEFAULT NULL COMMENT '分类ID',
`model` varchar(100) DEFAULT NULL COMMENT '型号',
`brand` varchar(50) DEFAULT NULL COMMENT '品牌',
`price` decimal(10,2) DEFAULT NULL COMMENT '日租金',
`deposit` decimal(10,2) DEFAULT NULL COMMENT '押金',
`location` varchar(100) DEFAULT NULL COMMENT '所在地区',
`image` varchar(255) DEFAULT NULL COMMENT '设备图片',
`description` text COMMENT '设备描述',
`status` tinyint(1) DEFAULT '1' COMMENT '状态 1-上架 0-下架',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
订单表(orders)是核心中的核心,字段设计如下:
sql复制CREATE TABLE `orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_no` varchar(32) DEFAULT NULL COMMENT '订单编号',
`user_id` int(11) DEFAULT NULL COMMENT '用户ID',
`equipment_id` int(11) DEFAULT NULL COMMENT '设备ID',
`start_date` date DEFAULT NULL COMMENT '租赁开始日期',
`end_date` date DEFAULT NULL COMMENT '租赁结束日期',
`total_price` decimal(10,2) DEFAULT NULL COMMENT '总租金',
`deposit` decimal(10,2) DEFAULT NULL COMMENT '押金',
`status` tinyint(1) DEFAULT '0' COMMENT '状态 0-待审核 1-已通过 2-已拒绝 3-租赁中 4-已归还 5-已取消',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
关于订单状态,我设置了一个比较完整的状态流转链路:用户提交订单 -> 管理员审核 -> 通过后变为“租赁中” -> 用户归还设备 -> 管理员确认归还 -> 状态变为“已归还”。这样每个环节都有人工确认,避免了纠纷。有人会说这种模式太重了,但对于农业设备这种高价值物品,人工审核是必要的。
2.2 MyBatis动态SQL实现租期冲突检测
排期冲突检测是这个系统里最容易写错的地方。最开始我蠢蠢地想把所有订单查出来然后在Java代码里做日期比较,结果数据量一大就卡顿,而且逻辑还很绕。后来我改成在SQL层面直接判断冲突。
核心的逻辑是:如果新订单的起止日期和已有订单的起止日期存在交集,那么两个订单就冲突了。 日期交集的条件可以翻译成:
code复制已有订单开始日期 <= 新订单结束日期 AND 已有订单结束日期 >= 新订单开始日期
这句话想明白之后,一条SQL就搞定:
xml复制<select id="checkConflict" resultType="java.lang.Integer">
SELECT COUNT(*)
FROM orders
WHERE equipment_id = #{equipmentId}
AND status IN (1, 3)
AND start_date <= #{endDate}
AND end_date >= #{startDate}
</select>
注意这里<和>在XML中要进行转义,写成<和>,新手往往在这里卡住导致报错。
还需要注意,订单状态只能统计“待审核”和“租赁中”这两种,“已拒绝”和“已归还”的订单不影响设备可用性。我最初就忘了在状态上做过滤,结果被拒绝的订单也占用了排期,被测试妹妹骂了一顿。
2.3 前端路由与权限控制的配合
前端路由我用了Vue Router的守卫来做登录拦截。这个比较简单:
javascript复制router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
if (to.path === '/login') {
next()
} else {
if (!token) {
next('/login')
} else {
next()
}
}
})
但光有前端路由守卫是不够的,后端接口同样要校验token。JWT这个方案用起来非常顺手,后端封装一个拦截器,在请求头里读取Authorization字段,如果解析失败就返回401状态码,前端Axios统一拦截这个状态码并跳转到登录页。
Axios统一封装这部分我专门抽了一个request.js出来,加上请求拦截器和响应拦截器,所有接口都走这个统一入口,好处是以后加token刷新、加错误提示、加loading状态,只需要改一处。
javascript复制import axios from 'axios'
import { Message } from 'element-ui'
import router from '@/router'
const request = axios.create({
baseURL: '/api',
timeout: 15000
})
request.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers['Authorization'] = token
}
return config
})
request.interceptors.response.use(
response => {
return response.data
},
error => {
if (error.response && error.response.status === 401) {
localStorage.removeItem('token')
router.push('/login')
}
Message.error(error.response?.data?.message || '请求失败')
return Promise.reject(error)
}
)
export default request
2.4 设备图片上传的本地存储方案
图片上传这个功能看似简单,其实坑不少。我采用的是“前端传文件 -> 后端接收 -> 保存到服务器本地目录 -> 返回访问URL”的方式,没有引入OSS等云存储服务,原因是这个项目尽量保持部署简单,不要让使用者还得去申请云服务账号。
后端接口大概是这样的:
java复制@PostMapping("/upload")
public Result upload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return Result.error("上传文件不能为空");
}
// 获取原始文件名
String originalFilename = file.getOriginalFilename();
// 生成新的文件名,避免重名
String fileName = System.currentTimeMillis() + "_" + originalFilename;
// 保存路径
String filePath = uploadDir + File.separator + fileName;
File dest = new File(filePath);
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
file.transferTo(dest);
// 返回访问路径
return Result.success("/files/" + fileName);
}
这里有个关键点:SpringBoot默认的静态资源映射并不会把你新建的uploadDir映射成可访问的URL,必须要手动配置一个资源映射器:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/files/**")
.addResourceHandler("file:" + uploadDir + File.separator);
}
}
记得uploadDir要写绝对路径,不然不同环境下可能在奇怪的地方创建目录。这个坑我踩过一次,当时在本地跑得好好的,部署到服务器上就死活找不到图片,最后发现是相对路径对应的目录不对。
2.5 租金的计算逻辑与精度处理
租金计算看起来只是“日租金 × 天数”,但这里有两个细节需要注意:
第一,日期差的计算方式。用户选择6月1日到6月3日,应该算几天?我一开始写的是(endDate - startDate),这样得到2天,但用户直觉上觉得1号到3号是3天。后来我处理成(endDate - startDate) + 1,这才是租赁行业通用的算法。
第二,金额精度。Java中用double做金额计算会出现0.1 + 0.2 = 0.30000000000000004这种问题,所以我用了BigDecimal来处理所有金额字段,保证精度无误。计算总租金的时候这样写:
java复制BigDecimal days = new BigDecimal(ChronoUnit.DAYS.between(startDate, endDate) + 1);
BigDecimal totalPrice = dailyPrice.multiply(days);
页面展示的时候再调用setScale(2, RoundingMode.HALF_UP)保留两位小数,避免出现199.999999这种难看的数字。
3. 项目搭建与部署全流程
3.1 环境准备与版本选择
做这个项目之前,先把环境统一好。我用的组合是:
| 软件 | 版本 | 说明 |
|---|---|---|
| JDK | 1.8 | SpringBoot 2.7.x对JDK8支持最完善 |
| Maven | 3.6.3 | 稳定版本 |
| MySQL | 8.0.x | 5.7也兼容,但8.0更推荐 |
| Node.js | 14.x | Vue 2项目在Node 14下编译最顺畅 |
| IDE | IDEA + VSCode | IDEA跑后端,VSCode写前端 |
强调一下:不要用JDK 17去跑SpringBoot 2.7.x。虽然理论上能跑,但有些旧版本的starter在反射操作上会报IllegalAccessException,排查起来非常费劲。另外Node版本也别用太新的,我试过Node 18编译Vue 2项目时会报digital envelope routines::unsupported这个错,原因就是新版Node的OpenSSL策略和老版本Webpack不兼容,解决办法有两种:把Node降到16以下,或者在package.json里改启动脚本加上SET NODE_OPTIONS=--openssl-legacy-provider。但说到底,换Node版本才是治本的方案。
3.2 后端启动步骤
后端启动我分几步来走:
第一步,执行项目里的agriculture_lease.sql文件,把数据库初始化好,默认账号密码在SQL文件里有注释说明,建议导入后改掉。
第二步,修改application.yml里的数据库连接信息。重点检查三处:数据库地址、用户名、密码。如果你本地MySQL的端口不是3306,记得同步修改:
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/agriculture_lease?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
第三步,在IDEA中打开项目,等待Maven依赖下载完成。这里有个常见问题:因为网络原因某些依赖下载失败,或者spring-boot-starter-parent版本下载不了。解决方法是给Maven配阿里云镜像:
xml复制<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
配置在~/.m2/settings.xml里。
第四步,运行AgricultureLeaseApplication.java的main方法,日志中出现Tomcat started on port(s): 8080就说明启动成功了。
3.3 前端启动步骤
前端流程:
bash复制cd frontend
npm install
npm run serve
npm install这里有个经验:如果你用了package-lock.json里锁定的版本,但公司网络访问npm官方源很慢,可以临时切换淘宝镜像源:
bash复制npm config set registry https://registry.npmmirror.com
再执行npm install,速度会快不少。国内开发者建议直接全局把registry切过去,省心。
启动成功后,浏览器访问http://localhost:8080,如果看到登录页,就说明前后端至少能独立运行了。
3.4 开发环境下的前后端联调配置
前后端分离开发时,最头疼的就是跨域问题。前端跑在8080,后端跑在8080后端的端口我就不重复了,这里假设前端是8080、后端是8080的话会冲突。实际开发中,前端跑在8080,后端跑在8081,此时要解决跨域。
我做了两件事:
第一,在后端加一个全局跨域配置类:
java复制@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("*");
config.addAllowedMethod("*");
config.addAllowedHeader("*");
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
第二,前端vue.config.js里配开发环境的代理,让前端请求的/api路径转发到http://localhost:8081:
js复制module.exports = {
devServer: {
port: 8080,
proxy: {
'/api': {
target: 'http://localhost:8081',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
}
}
}
注意跨域配置和代理二选一即可,如果你配了代理就不需要再配CORS,两个都配也不会有问题,但容易混淆,建议开发时用代理,生产时用Nginx反向代理。
3.5 生产环境部署:Nginx + Jar包方式
生产环境部署我推荐用“Nginx托管前端静态文件 + systemd管理后端Jar包”这种方式。
后端打包:
bash复制mvn clean package -DskipTests
打出来的agriculture-lease-0.0.1-SNAPSHOT.jar放在/opt/agriculture-lease目录下,然后创建一个systemd服务文件:
code复制[Unit]
Description=Agriculture Lease System
After=syslog.target
[Service]
User=root
ExecStart=/usr/local/jdk8/bin/java -jar /opt/agriculture-lease/agriculture-lease-0.0.1-SNAPSHOT.jar
SuccessExitStatus=143
Restart=on-failure
[Install]
WantedBy=multi-user.target
前端构建:
bash复制cd frontend
npm run build
构建产物在dist目录,把dist目录里的内容上传到服务器/usr/share/nginx/html/agriculture目录下,然后修改Nginx配置:
code复制server {
listen 80;
server_name your-domain.com;
location / {
root /usr/share/nginx/html/agriculture;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:8081/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /files/ {
alias /opt/agriculture-lease/files/;
}
}
这里要特别说明两个点:
-
try_files $uri $uri/ /index.html;这行是SPA应用部署的关键。Vue Router默认用history模式时,用户直接访问/equipment/list这个地址,Nginx会去找/usr/share/nginx/html/agriculture/equipment/list这个文件,找不到就返回404。加上这行配置之后,任何找不到的路径都会回退到index.html,由前端路由接管,404问题就消失了。 -
location /api/配置里的proxy_pass http://127.0.0.1:8081/;最后的斜杠不能省。如果写了http://127.0.0.1:8081,那么请求/api/equipment/list会转发成/api/equipment/list;如果带了斜杠写成http://127.0.0.1:8081/,就会把/api前缀去掉,转发成/equipment/list。这里必须带斜杠才能和后端的@RequestMapping路径对上。
4. 常见问题与排查技巧实录
4.1 MySQL启动失败或连接不上的排查思路
这个问题的出场率实在太高了。很多同学在本机第一次安装MySQL,照着网上的教程装完,结果服务起不来,或者能起来但客户端连接报错。
我整理了一个排查顺序:
- 检查服务是否启动:Windows下在服务管理器找MySQL服务,macOS/Linux下用
ps -ef | grep mysql确认进程状态。 - 检查端口占用:
netstat -ano | findstr 3306(Windows)或lsof -i:3306(macOS/Linux),如果端口被占用,在my.cnf里改端口或者杀掉占用进程。 - 检查root用户密码:MySQL 8.0默认密码认证机制是
caching_sha2_password,如果客户端工具版本太旧可能连不上,需要改用mysql_native_password。
如果是用SpringBoot启动时连不上MySQL,看到Access denied for user 'root'@'localhost',大概率是密码错了或者没有远程访问权限。本地开发时经常需要给root开远程访问权限,执行:
sql复制ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码';
FLUSH PRIVILEGES;
4.2 MyBatis配置了mapper扫描却一直报找不到方法
这个问题的典型表现是项目启动时报Invalid bound statement (not found)。
排查步骤:
第一,确认Mapper接口和XML文件是否在同一包路径下。SpringBoot默认只扫描@MapperScan注解指定的包,如果你把XML放在了resources目录下而接口在com.xxx.mapper包下,那么XML文件必须在resources/com/xxx/mapper/目录下,且文件名要和接口名完全一致。
第二,检查application.yml里是否配置了mybatis的mapper-locations:
yaml复制mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.xxx.entity
如果XML文件放在resources/mapper目录下,这个配置就指向这里。
第三,还有一个容易忽略的点:检查pom.xml里是否配置了resources过滤。有时候因为配置了<resources>导致resources目录下的XML文件没有被打包进target,在IDEA里本地启动没问题,但打成Jar包之后就报找不到。解决方法是显式在pom.xml里加上:
xml复制<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.yml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
4.3 MyBatis批量插入报错或数据没写进去
有朋友在实现设备批量导入时报了SyntaxError,或者插入时“没报错但数据没进去”。这个问题在MyBatis官方文档里有专门的章节,核心是foreach标签的用法。
批量插入的XML写法:
xml复制<insert id="batchInsert" parameterType="list">
INSERT INTO equipment (name, category_id, model, brand, price, deposit)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.name}, #{item.categoryId}, #{item.model}, #{item.brand}, #{item.price}, #{item.deposit})
</foreach>
</insert>
容易踩坑的地方是:
collection属性写值必须为list,如果方法参数名不是list,需要加@Param("list")注解。- MySQL默认支持的最大SQL包大小是
max_allowed_packet,如果一次插入的数据特别多,可能超过限制报错,需要调大MySQL配置。 - 如果用了MyBatis-Plus,可以用
IService.saveBatch()方法,内部自动分批插入,省心很多。
至于“没报错但数据没进去”,最常见的原因是对外提供服务的方法没有加@Transactional注解,或者在Service里吞掉了异常。另外,MyBatis默认是不开启批量执行的,你在Mapper接口方法上写@Insert注解直接拼SQL反而更直观。
4.4 Vue项目启动报错:digital envelope routines::unsupported
这是一个非常经典的Node新版本兼容问题。Vue 2项目依赖的Webpack version 4在Node 17及以上版本中会因为OpenSSL策略变化报错。
解决办法分两种:
一种是临时方案,修改package.json里的dev脚本:
json复制"scripts": {
"serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve"
}
macOS/Linux下面是:
json复制"scripts": {
"serve": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve"
}
另一种是治本方案,直接安装Node 16 LTS版本。不建议在新的Node版本上硬扛,因为除了这个问题,后续还可能遇到其他依赖兼容性问题。
4.5 前后端联调时接口返回404或CORS报错
我整理了一个“接口访问速查表”,排查时按优先级从高到低走一遍:
| 现象 | 可能原因 | 排查措施 |
|---|---|---|
| 返回404,后端日志无请求记录 | Nginx或代理配置路径不对,请求根本没到后端 | 检查proxy_pass路径;直接访问后端接口确认本身可通 |
| 返回404,后端日志有记录 | Controller里的@RequestMapping路径没对上 | 用Postman直接测接口路径,对比前端请求地址 |
| 提示CORS错误 | 跨域配置没生效或代理没配 | 确认后端CorsConfig生效;开发环境改用代理方式 |
| 返回401 | token过期或没传 | 检查Axios请求头是否带上了Authorization |
| 返回500,日志一堆SQL报错 | 数据库表或字段不对,SQL语句有拼写问题 | 打开MyBatis SQL日志打印,把实际执行的SQL拿出来执行一遍 |
这里关于MyBatis日志打印,顺带分享一个配置方法。在application.yml里加:
yaml复制logging:
level:
com.xxx.mapper: debug
这样MyBatis会打印所有执行的SQL语句和参数,排查问题效率直接拉满。生产环境注意别开debug,否则日志会刷得非常大。
4.6 前后端接口字段对不上导致的隐性Bug
这类问题的表现是:页面看起来正常,但提交后数据丢失或者展示错乱。比如后端返回的字段叫totalPrice,前端代码里却写成了totalprice。Vue模板中大小写不敏感时,{{ totalPrice }}和{{ totalprice }}都不会报错,但数据渲染就是不对。
我的经验是:前后端约定好接口文档,哪怕再简单的系统也要用Apifox或Postman把每个接口的入参、出参过一遍。特别是字段命名风格,后端用驼峰,前端也统一用驼峰,或者全部转成下划线再在JSON序列化时加@JsonProperty注解映射,这个细节在团队合作和多端适配时很重要。
5. 项目扩展与个人经验总结
5.1 后续功能扩展方向
这个系统跑通之后,有几个明显的扩展方向:
一是接入微信小程序。前后端分离的好处在这里体现得很明显,后端接口可以直接复用,只需要新开一个小程序前端项目就行。Vue这边可以用uni-app,一套代码同时输出H5和小程序,成本很低。
二是增加消息通知模块。租约即将到期、审核结果通知、归还提醒这些场景,可以通过接入WebSocket或者第三方短信服务来实现。对于农业设备租赁这种偏线下交付的业务,短信通知比站内信更实用。
三是设备定位与状态监控。如果设备价值高,可以加一个设备定位模块,对接物联网设备上报的经纬度数据,在地图上展示设备位置。这属于增值功能,但实现起来也就是多一张设备定位表和一个消息接收接口的事。
四是增加支付闭环。目前系统里订单状态靠人工流转,如果接入了微信支付或支付宝支付,可以在用户下单后直接在线支付押金和租金,系统自动确认支付结果并更新订单状态。技术上可以用支付平台的SDK,回调处理那一块需要仔细设计,防止重复回调导致订单状态错乱。
5.2 项目源码使用建议
如果你拿到的是完整源码,我建议按下面的顺序去看代码,而不是从头读到尾:
- 先看数据库设计文档和SQL文件,理解表结构和状态字段的含义。
- 再看后端的Controller层,把每个接口的URL和功能对应上。
- 再看Service层的核心业务逻辑,特别是订单创建和排期冲突检测这两块。
- 最后看前端页面,重点理解Vue Router的路由配置和Axios的封装。
改代码的时候,我强烈建议先跑通一个完整流程:注册用户 -> 登录 -> 添加设备 -> 提交订单 -> 管理员审核 -> 确认归还。把这条链路走通了,你对系统的理解就到位了,之后再去改任何一个模块都有底气。
5.3 踩坑多次之后的心得
最后聊几个我在本地跑通整套项目时的直觉判断。
一个体会是,这种前后端分离的项目,真正耗时间的不是写业务代码,而是环境问题和联调问题。SpringBoot和Vue各自单独跑都很简单,但一旦要把它们串起来,跨域、代理、路径映射、字段命名、端口冲突,随便一个点都能卡住大半天。所以我在自己做项目时,第一步永远是先把前后端跑通,哪怕页面是空白的,也要确认接口能通,再开始写业务逻辑。这个顺序问题不要反过来。
另一个体会是,MyBatis的XML文件比你想的更脆弱。多个条件组合的动态SQL,一个<if test>拼错就可能导致整个查询失效。我建议凡是超3个条件的查询,先在Navicat里用原生SQL跑一遍,再改写为XML里的动态SQL,这样出错率会低很多。
最后再多说一句,做项目不要光为了“跑通”而跑通。顺手把统一异常处理、日志记录、请求参数校验这些基础工程能力加上,这些在设计文档里不会写,但面试时、工作后都是加分项。比如我加了一个基于@ControllerAdvice的全局异常处理器,接口报错时返回统一的JSON格式,前端就不用写一堆try-catch了。
现在这套“前端接口返回统一格式 + 后端全局异常处理 + 固定错误码”的模式,已经成为我做所有SpringBoot项目的标配了。从这个角度来说,这个农业设备租赁系统不只是个课程设计,更像是一个能反复参考的后端脚手架。以后你们拿到其他项目的源码,其实也可以用同样的思路去拆解:先跑通,再读关键逻辑,最后加自己想要的模块。这样练习几次,前后端分离项目的套路就完全吃透了。
