一个能跑起来的全栈项目,跟一份只能在PPT里演示的代码,差距往往就在几个细节上。这次要拆解的这套“车辆管理系统”,走的是SpringBoot后端+Vue前端+MySQL数据库的经典组合,目标很明确:拿到手就能启动,启动就能录入数据,录入完就能看到报表。不是概念验证,不是教学demo,是一个真正能让行政、车队管理员用起来的工具。
我花了两天时间把整个项目从下载到跑通,又把里里外外的设计逻辑捋了一遍。这篇文章不只是告诉你“怎么启动”,更想跟你聊聊这套系统背后的设计取舍、数据库字段为什么要那样建、权限控制拦在了哪一层、以及那些不跑一遍绝对发现不了的坑。如果你正准备做类似的单体全栈管理系统,或者手头刚好需要一套车辆信息化的基础代码,这篇文章应该能帮你少走不少弯路。
1. 项目整体拆解:这套系统的定位与技术选型逻辑
1.1 核心需求解析:它要解决的到底是什么
车辆管理这件事,往小了说是记录车牌和司机,往大了说牵扯到出车审批、油耗统计、维保提醒、保险到期、违章查询、驾驶员档案、年检跟踪。这套系统瞄准的是中小企业的真实痛点:车辆信息散落在Excel表、微信群和纸质审批单里,想查一个数据得翻半天聊天记录。
所以系统的核心业务域可以拆成四块:
- 基础档案管理:车辆信息、驾驶员信息、部门信息,这是最底层的静态数据。
- 业务流转管理:出车申请、审批、归还登记,这是使用频率最高的动态数据。
- 运维状态管理:维修保养记录、保险到期提醒、年检状态跟踪。
- 统计与导出:出车次数统计、里程统计、费用汇总,给管理者做决策参考。
从这套结构能看出来,它瞄准的不是那种日均几千单的高并发调度系统,而是“把线下纸质流程搬到线上”的轻量级管理系统。理解了这一点,再看技术选型就清晰了:这套系统的数据量级和并发规模,单机MySQL加一个SpringBoot进程完全撑得住,Vue做管理后台也足够灵活,根本不需要引入微服务和消息队列那些重武器。
1.2 为什么选SpringBoot+Vue+MySQL这套黄金组合
先说后端,SpringBoot确实是“约定大于配置”的典型代表。内嵌Tomcat意味着不用单独装服务器,java -jar就能跑起来;自动配置机制帮我们把数据源、事务管理器、MyBatis这些组件全部默认装配好。对中小型管理项目来说,它最大的价值是“省心”。
再说前端,Vue的响应式数据绑定让表单校验、列表筛选、状态切换这类交互写得非常顺畅。配合Element UI这种组件库,表格、弹窗、表单、分页这些后台管理系统里用到吐的组件都是现成的,开发效率确实高。
数据库选MySQL,就一个字:稳。它没有PostgreSQL那么多高级特性,但正因为功能简洁、资料海量、顺手可用,反而成了中小项目最不容易出错的“标准答案”。
这套组合还有一个现实考量——招人容易。会Java、会Vue、会MySQL的开发者在市场上占比很高,项目后续如果需要维护或者二次开发,找人不愁。技术栈冷门的项目,往往做着做着就变成“只有我能维护的遗产”。
1.3 项目目录结构:源码该怎么看
拿到源码后第一件事,是看懂目录结构。这套系统前后端分离,端口配置也遵循了惯例:前端跑8080,后端跑8888,MySQL默认3306,通过代理解决跨域问题。
code复制vehicle-management/
├── backend/ # SpringBoot后端
│ ├── src/main/java/
│ │ └── com/vehicle/mgmt/
│ │ ├── controller/ # 控制层,接收HTTP请求
│ │ ├── service/ # 业务逻辑层
│ │ ├── mapper/ # MyBatis数据访问层
│ │ ├── entity/ # 数据库实体映射
│ │ ├── config/ # 配置类(跨域、拦截器)
│ │ └── VehicleApplication.java # 启动类
│ └── src/main/resources/
│ ├── application.yml # 核心配置文件
│ └── mapper/ # MyBatis XML文件
├── frontend/ # Vue前端
│ ├── src/
│ │ ├── api/ # 接口请求封装
│ │ ├── views/ # 页面组件
│ │ ├── router/ # 路由配置
│ │ ├── store/ # Vuex状态管理
│ │ └── main.js
│ └── package.json
└── sql/
└── vehicle_management.sql # 数据库初始化脚本
我的习惯是先打开application.yml,再看sql目录下的建表脚本,然后是前端的router和api两个文件夹。这样一轮下来,整个系统的运行路径就串起来了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与启动流程:从零到可运行
2.1 开发环境版本配套建议
“可直接运行”这四个字听起来简单,实际上一大半的启动失败都出在版本不配套上。先把推荐版本组合列出来:
| 组件 | 推荐版本 | 备注 |
|---|---|---|
| JDK | 1.8 或 11 | SpringBoot 2.x 对这两个版本支持最稳 |
| Maven | 3.6+ | 3.8以上对镜像配置要求更严 |
| Node.js | 14.x 或 16.x | 对Vue 2项目兼容性最好 |
| npm/yarn | npm 6+ / yarn 1.x | Vue CLI构建依赖 |
| MySQL | 5.7 或 8.0 | 5.7更稳,8.0需注意密码加密插件 |
| Navicat | 任意版本 | 也可用命令行工具导入SQL |
| IDEA | 2021+ | 社区版完全够用 |
这里特别想提醒JDK版本的问题。如果项目用的SpringBoot 2.x,就别上JDK 17了——不是跑不起来,而是很多老依赖在JDK 17下会出现反射访问异常,排错的过程相当折磨人。老老实实用JDK 8,最不容易出幺蛾子。
2.2 MySQL初始化:建库、建用户、导入SQL脚本
这套系统的SQL脚本是整个项目的“根”,脚本建不起来,后面全白搭。拿到vehicle_management.sql之后,打开看一眼开头几行,确认它的建库语句:
sql复制CREATE DATABASE IF NOT EXISTS vehicle_management DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE vehicle_management;
这里的utf8mb4不是笔误,是utf8的完整版,能存emoji和一些特殊字符。在MySQL 5.5.3以上版本中,utf8mb4才是真正的全量UTF-8支持,普通的utf8字符集在存生僻字或符号时会报错。
导入命令很简单:
bash复制mysql -u root -p < vehicle_management.sql
用Navicat的话,右键数据库选择“运行SQL文件”,导入完成后刷新就能看到表结构。建议重点看一下这几个核心表的设计,后面写代码要用到的字段都在这里:
vehicle_info:车辆基本信息表,含车牌、品牌、座位数、购买日期等。driver_info:驾驶员档案表,含驾驶证号、准驾车型、入职时间等。vehicle_apply:出车申请表,含申请人、用车事由、预计归还时间等。vehicle_maintenance:维修保养记录表。
2.3 后端启动:Maven依赖加载与项目配置
后端启动是整个流程里最容易出问题的一环。IDEA里打开backend目录,等待Maven自动下载依赖。首次加载耗时可能比较久,这取决于网络速度,耐心等就是了。我建议优先检查两件事:
打开application.yml,确认数据库连接配置是匹配本机的:
yaml复制server:
port: 8888
spring:
datasource:
url: jdbc:mysql://localhost:3306/vehicle_management?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 你的数据库密码
driver-class-name: com.mysql.cj.jdbc.Driver
有几个参数值得解释一下。useSSL=false是因为本地开发环境MySQL默认没配置SSL证书,不关掉会警告甚至报错;serverTimezone=Asia/Shanghai必须加,否则MySQL 8.0会因为你本地时区和数据库默认时区不一致,在时间字段上闹脾气;useUnicode=true&characterEncoding=utf8是保证中文字符不乱码的关键。
然后直接运行启动类VehicleApplication.java。正常启动后在控制台能看到SpringBoot的标志和端口信息。如果端口8888被占用,改application.yml里的server.port就可以了。
2.4 前端启动:依赖安装与代理配置
前端用的是npm生态,需要先确保Node.js装好了,然后安装依赖:
bash复制cd frontend
npm install
这里有两个常见问题。第一,npm install报错的话,先看是不是网络问题,国内用户建议把镜像源切到淘宝镜像:
bash复制npm config set registry https://registry.npmmirror.com
切换完再装一次,速度会快很多。
第二,package.json里的依赖建议先看一遍版本。如果项目用的是Vue 2.x,那么Node 17以上版本在构建时可能会报数字签名算法相关的错误,这时候需要设置环境变量绕过:
bash复制export NODE_OPTIONS=--openssl-legacy-provider
Windows用户写:
bash复制set NODE_OPTIONS=--openssl-legacy-provider
依赖装完后启动开发服务:
bash复制npm run serve
启动成功后,浏览器输入localhost:8080,正常情况下就能看到登录页了。登录页面出来后,后端进程保持运行,先别急着输入账号密码,下一节先花几分钟把数据库表结构和系统代码线路理清楚,再去登录操作,效率会高得多。
3. 数据库设计与核心接口实现
3.1 表结构设计与ER关系拆解
打开SQL脚本你会发现,这套系统的表设计处处透着“够用就好”的实用主义。核心表的关系也不复杂,可以理解为:
- 车辆表是中心,它跟出车申请表是一对多的关系,一辆车有多次出车申请;
- 驾驶员表跟出车申请表也是一对多的关系,一个司机有多次出车记录;
- 维修保养表挂在车辆表下面,一辆车的维修记录按时间排列。
下面把最核心的vehicle_info表结构拉出来看看,字段设计很典型:
sql复制CREATE TABLE `vehicle_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`plate_number` varchar(20) NOT NULL COMMENT '车牌号码',
`brand` varchar(50) DEFAULT NULL COMMENT '品牌型号',
`car_type` varchar(20) DEFAULT NULL COMMENT '车辆类型(轿车/SUV/货车)',
`seat_count` int(11) DEFAULT NULL COMMENT '座位数',
`purchase_date` date DEFAULT NULL COMMENT '购买日期',
`status` tinyint(4) DEFAULT '1' COMMENT '状态 1可用 0不可用',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_plate_number` (`plate_number`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆信息表';
几个设计细节值得留意。plate_number加了唯一索引,这是业务上合理的限制——同一个车牌不能录两遍。status用tinyint而不是varchar存中文,是数据库设计的通用手法,状态码定成数字,程序里定义常量去映射含义,改起来方便。create_time和update_time都用了数据库默认值,省去手动填写的麻烦。
sql复制CREATE TABLE `vehicle_apply` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`vehicle_id` int(11) DEFAULT NULL COMMENT '车辆ID',
`driver_id` int(11) DEFAULT NULL COMMENT '驾驶员ID',
`apply_user` varchar(50) DEFAULT NULL COMMENT '申请人',
`apply_reason` varchar(255) DEFAULT NULL COMMENT '用车事由',
`start_time` datetime DEFAULT NULL COMMENT '预计出发时间',
`end_time` datetime DEFAULT NULL COMMENT '预计归还时间',
`status` tinyint(4) DEFAULT '0' COMMENT '审批状态 0待审批 1已通过 2已拒绝',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_vehicle_id` (`vehicle_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='出车申请表';
出车申请表字段虽然不多,但把流程管理需要的信息都覆盖到了。这里特别用了外键关联字段vehicle_id和driver_id,但不建物理外键约束,这也是国内企业项目的常见做法——减少了锁竞争,也方便了后续的数据清理。
3.2 后端接口清单与核心逻辑黑话
把后端的Controller层翻一遍,能整理出一份完整的接口清单。按模块分比较清晰:
| 模块 | 接口路径 | 作用 |
|---|---|---|
| 车辆管理 | /vehicle/list | 车辆列表(分页) |
| 车辆管理 | /vehicle/add | 新增车辆 |
| 车辆管理 | /vehicle/update | 修改车辆信息 |
| 车辆管理 | /vehicle/delete/ | 删除车辆 |
| 出车管理 | /apply/list | 出车申请列表 |
| 出车管理 | /apply/add | 提交出车申请 |
| 出车管理 | /apply/approve | 审批出车申请 |
| 驾驶员管理 | /driver/list | 驾驶员信息列表 |
| 系统登录 | /user/login | 登录接口 |
| 统计报表 | /statistics/vehicle | 车辆使用率统计 |
核心业务逻辑都集中在Service层。拿出车申请审批来说,伪代码大概是这样的:
java复制public boolean approveApply(Integer applyId, Integer status) {
// 1. 查询申请记录
VehicleApply apply = applyMapper.selectById(applyId);
// 2. 校验申请是否存在、是否已经是终态
if (apply == null || apply.getStatus() != 0) {
throw new BusinessException("申请记录不存在或已被处理");
}
// 3. 更新状态
apply.setStatus(status);
// 4. 如果审批通过,把车辆状态改成“使用中”
if (status == 1) {
vehicleInfoService.updateStatus(apply.getVehicleId(), 0);
}
return applyMapper.updateById(apply) > 0;
}
这套代码写得很直白,但有个值得学习的设计:审批通过后会联动更新车辆状态。比如“浙A12345”被申请出去了,车辆状态从“可用”变成“使用中”,这样其他人申请时这个车牌就不会出现在可选列表里。数据联动写在Service层,而不是让前端去调用多个接口,业务闭环在后端就完成了。
3.3 前后端联调:跨域配置与API数据结构约定
前后端分离项目,联调阶段第一个坎就是跨域。浏览器的同源策略限制了8080端口的前端页面去直接请求8888端口的后端接口。这套系统的处理方式是在后端加了跨域配置类:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:8080")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.maxAge(3600);
}
}
然后前端在vue.config.js里配置代理:
javascript复制module.exports = {
devServer: {
port: 8080,
proxy: {
'/api': {
target: 'http://localhost:8888',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}
这里有个中国开发者都懂的经典坑:为什么/api要重写掉?因为后端接口路径没有/api前缀。前端的请求地址是/api/vehicle/list,代理转发后变成了http://localhost:8888/vehicle/list,路径被重写对齐了。两边的接口路径习惯不同,用一个代理中转抹平差异。很多新手第一次遇到跨域问题就在这卡住。实际上用了代理之后,请求根本没有经过浏览器直接发给后端,而是从代理服务器转发的,规避了浏览器同源限制。
接口返回的数据结构也很统一:
json复制{
"code": 200,
"message": "操作成功",
"data": {
"total": 50,
"records": [...]
}
}
前端封装了axios实例,请求拦截器自动加上token,响应拦截器统一判断code字段。这套东西看起来简单,但它保证了前端所有接口调用都走相同的逻辑,不用每个页面重复写错误处理。
4. 前后端核心实现细节与关键页面实战
4.1 前端权限控制:动态路由与登录态管理
管理系统的前端不能只是“长得好看”,它还得实现基于角色的权限控制。这套系统用Vue Router和Vuex配合完成权限管理。先说登录流程:
- 用户输入账号密码,前端调用
/user/login接口。 - 后端验证通过,返回token和用户角色信息。
- 前端把token存到
localStorage,把用户信息提交到Vuex。 - 页面跳转到首页。
路由守卫挂在全局前置守卫里:
javascript复制router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
if (to.path === '/login') {
next()
} else {
if (!token) {
next('/login')
} else {
next()
}
}
})
这套守卫拦截了所有未登录的访问,逻辑很清晰:没token就踢回登录页。但说实话,只校验“有没有token”是最基础的方案。生产级系统的权限控制必须在后端也校验接口权限,因为前端按钮、路由都可以改代码绕过。这个项目至少在接口层面做了登录拦截,后端有拦截器校验Token,比只靠前端控制安全得多。
4.2 车辆管理页面:表格渲染、分页与条件搜索
车辆管理页面是后台管理系统最典型的组合:搜索表单+数据表格+分页。页面加载时调用vehicle/list接口,传的是分页参数:
javascript复制getVehicleList({ pageNum: 1, pageSize: 10, plateNumber: '' })
后端用MyBatis分页插件,收到参数后自动添加LIMIT语句。前端拿到data.records渲染表格:
vue复制<el-table :data="tableData" border stripe>
<el-table-column prop="plateNumber" label="车牌号码" width="120"></el-table-column>
<el-table-column prop="brand" label="品牌型号"></el-table-column>
<el-table-column prop="seatCount" label="座位数"></el-table-column>
<el-table-column label="车辆状态">
<template slot-scope="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">
{{ scope.row.status === 1 ? '可用' : '不可用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" type="primary" @click="handleEdit(scope.row)">编辑</el-button>
<el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
这里值得学习的是状态列用el-tag组件展示,不同状态值显示不同颜色,比纯文字直观很多。这个细节虽然在技术上很简单,但用户体验上的提升很明显,管理后台界面对信息的“一眼可读性”要求很高。
新增和编辑表单共用一个弹窗组件,通过dialogTitle字段区分是新建还是修改。表单校验规则是这样的:
javascript复制rules: {
plateNumber: [
{ required: true, message: '请输入车牌号码', trigger: 'blur' },
{ pattern: /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙青贵川藏陕琼宁吉]([A-H])?[A-Z][A-Z0-9]{4,5}[A-Z0-9挂学警港澳]$/, message: '车牌格式不正确', trigger: 'blur' }
]
}
车牌正则这一块,写得好不好直接影响用户体验。上面这个正则覆盖了普通蓝牌、新能源绿牌、挂学警港澳军牌等绝大多数场景,新手可以直接抄。
4.3 出车审批流程:状态流转的前端交互实现
出车审批是整个系统业务味道最浓的功能。流程设计上,出车申请单流转三个状态:待审批、已通过、已拒绝。列表页展示的时候,根据状态值决定操作列显示哪些按钮:
vue复制<el-table-column label="操作" width="180">
<template slot-scope="scope">
<el-button type="success" size="mini" @click="handleApprove(scope.row)" v-if="scope.row.status === 0">通过</el-button>
<el-button type="danger" size="mini" @click="handleReject(scope.row)" v-if="scope.row.status === 0">拒绝</el-button>
<el-tag v-else :type="scope.row.status === 1 ? 'success' : 'danger'">
{{ scope.row.status === 1 ? '已通过' : '已拒绝' }}
</el-tag>
</template>
</el-table-column>
只在自己状态为“待审批”时显示审核按钮,这条规则很简单,但是代码质量的分水岭就在这些细节里。有人会把按钮都渲染出来,再靠disabled属性来控制,结果各状态都能看到灰色按钮,界面一团糟。用v-if直接不渲染,干净利落,这在Vue开发中是值得坚持的小习惯。
审批通过后,车辆状态联动变为“不可用”,这个逻辑服务端已经处理了。前端这边,审批接口调用成功后只需要刷新当前列表即可:
javascript复制async handleApprove(row) {
await this.$confirm('确认通过该申请吗?', '提示', { type: 'warning' })
await this.$http.post('/apply/approve', { id: row.id, status: 1 })
this.$message.success('审批通过')
this.fetchList()
}
用户体验的关键因素之一是弹窗确认。删除车辆、拒绝申请这些有影响的操作,如果敲一下回车就执行了,用户心里没底。加一个确认框,成本很低,但能避免很多误操作导致的麻烦。
5. 常见启动/运行问题与排查方法
5.1 端口占用与进程清理
启动项目时遇到“Port 8888 was already in use”是最常见的报错。解决办法不复杂,找到占用进程清理掉就行。
Windows下用命令找进程:
bash复制netstat -ano | findstr 8888
拿到PID后:
bash复制taskkill /f /pid 进程号
Mac或Linux下用lsof:
bash复制lsof -i :8888
kill -9 进程号
这里有一个小建议:把后端默认端口设成8888不一定会被占用,但前端的8080经常被各种开发服务器抢占。要是前端启动报错说8080被占用,Vue CLI会问你是否换到8081,直接按回车同意就行。不用去跟其他进程抢端口。
5.2 MySQL连接报错与密码问题
数据库连不上导致的报错信息五花八门,最常见的两类:
Communications link failure错误。 大半是MySQL服务没启动。Windows用户去“服务”里找到MySQL服务,启动它。Mac用户用brew services start mysql启动。
Access denied for user 'root'@'localhost'错误。 密码不对,或者用了MySQL 8.0的密码加密插件。最省事的办法是用root登录重置密码:
sql复制ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '新密码';
FLUSH PRIVILEGES;
如果用的是MySQL 5.7,则:
sql复制SET PASSWORD FOR 'root'@'localhost' = PASSWORD('新密码');
很多项目跑不起来,其实不是代码问题,是MySQL的root密码和自己记的不一致。遇到过一位朋友,密码里有个@符号,在application.yml里没做任何转义,直接暴露在URL里了,于是连接串被解析错了。凡是密码里含特殊字符的,建议要么把密码改简单点用于本地开发,要么用spring.datasource.url的encode编码写法处理。为了省事,本地开发直接用简单密码好了。
5.3 前端编译报错与依赖版本冲突
前端问题集中在npm install和npm run serve两个环节。
npm install报ERR! code ERESOLVE,最常见的场景是依赖版本本身存在冲突。解决办法也不难,用--legacy-peer-deps参数绕过冲突检查:
bash复制npm install --legacy-peer-deps
这个命令的本质是跳过npm 7以上版本新增的peerDependencies严格检查,向后兼容旧项目。
npm run serve报error:0308010C:digital envelope routines::unsupported,这个报错是OpenSSL导致的,Node版本问题。上面提到过的处理办法,在这里再加一次强调:
bash复制export NODE_OPTIONS=--openssl-legacy-provider
或者在项目根目录新建.env文件,写入NODE_OPTIONS=--openssl-legacy-provider。
还有一种情况是启动后页面白屏,按F12打开控制台看到一堆JS报错,这种情况90%是接口数据返回格式与前端预期不一致。比如后端data字段里是list和total,前端非要records和total,取不到值就白屏。先看接口返回的JSON长什么样子,再对照前端代码里的取数逻辑,总能找到问题。
如果运气不好遇到第二种情况,调试的核心思路是看数据流的形状。用Chrome DevTools的Network面板点击XHR过滤出接口请求,看Response和Preview标签页的实际返回,再对照前端的tableData是怎么赋值的,数据链路通了,页面自然就出来了。
5.4 前后端联调404/405问题
接口404通常有两种可能:第一,后端接口路径写错了,Controller上的@RequestMapping("/vehicle")和方法的@GetMapping("/list")拼接出来的路径,跟前端请求的/vehicle/list对不上;第二,拦截器拦掉了请求,比如后端Spring Security配置了/api/**需要认证,前端访问时没带token就被拒了。
405一般是请求方式不匹配。前端用了POST,后端接口是@GetMapping,这就会出现Method Not Allowed。排查方式很简单,看后端控制台的日志,Spring日志会明确提示“Request method 'POST' not supported”。
联调这活儿,80%的时间其实是在对各种拼写。Java的驼峰命名和接口路径的对齐、前端请求参数名跟后端实体字段名的对齐——串起来了事就顺了。我建议前端同学的接口请求统一封装在一个api目录下,每个接口的路径、方法、参数都写清楚,这样排查时一查一个准。
6. 项目二次开发与生产部署经验
6.1 新增业务模块的切入点
如果要在现有系统中新增功能模块,比如“加油记录管理”,该从哪里动手?答案是后端从数据表开始,逐层往上走:
- 建表:在
vehicle_management库里新增fuel_record表,设计好字段和索引。 - 写实体类:在
entity目录下创建FuelRecord.java,字段与表结构对齐。 - 写Mapper:创建
FuelRecordMapper.java接口和XML文件,写好增删改查和分页查询SQL。 - 写Service:在
service层处理业务逻辑,比如录入加油记录后联动更新车辆的累计里程、总油耗。 - 写Controller:暴露REST接口,设计好路径、请求参数和返回结构。
- 前端开发:在
views目录下新建页面组件,在router里加路由,在api里加接口调用方法。
这一套流程,就是Spring Boot项目中最常见的“从数据库到页面”开发路径。熟练之后,新增一个模块就是一天上下的事。
6.2 构建生产包与部署要点
开发环境下跑起来已经不容易,生产部署又有自己的坑。
后端构建可执行Jar包:
bash复制cd backend
mvn clean package -DskipTests
target目录下会生成vehicle-management.jar,上传到服务器执行:
bash复制java -jar vehicle-management.jar --spring.profiles.active=prod
这里推荐用--spring.profiles.active参数切环境配置。生产环境的数据库连接串、密码、日志级别不该写在公共配置文件里,单独建一个application-prod.yml,内容更长、连接串包含更多参数,这种做法更规范。
前端构建静态文件:
bash复制cd frontend
npm run build
构建产物在dist目录。生产部署策略有两个方案:
- 方案一:Nginx托管前端静态文件 + 反向代理后端接口。
nginx复制server {
listen 80;
server_name your-domain.com;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:8888/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
关键是try_files那一行,Vue是单页应用,刷新页面时前端路由会向后端请求路径资源,不加这行就会404。location /api/的proxy_pass注意末尾的/,它会把/api/前缀去掉再转发,逻辑跟开发环境代理一致。
- 方案二:后端Jar同时托管前端。
把dist目录里的文件复制到SpringBoot的src/main/resources/static/下,重新打包。缺点是每次改前端都要全量重新部署后端。对小型项目尚可接受,但推荐用方案一,前后端耦合度更低。
6.3 系统上线前的初始化工作清单
部署完成后别急着交付,有几件事一定要做:
第一,用mysqldump备份数据库,验证恢复流程能跑通。数据无价,这句话吐槽了无数遍,但每次出事前的侥幸心理总让人忘记备份。
第二,修改系统所有默认密码。如果这套系统的初始管理员账号是admin/admin123,上线不换密码等于白装防火墙,系统自带后门。
第三,检查日志文件路径的读写权限。后端服务是用什么用户启动的,日志目录就得让这个用户可写,否则服务可能直接崩掉。
第四,用生产环境的域名或者IP访问一遍全流程,别只在localhost上验证过就觉得万事大吉。很多问题(比如前端路由刷新404、静态资源加载失败、接口地址写死localhost)只在换了访问地址后才暴露。
7. 实操心得:两天下来我印象最深的三件事
第一,这套系统的代码质量在企业级“可直接运行”项目里算中等偏上的。视野不局限在“能跑”,业务逻辑里有联动(审批通过后车辆状态自动改)、有校验(状态终态后不能重复审批)、有规范(时间字段统一用datetime、软删除标记留了字段位)。这种细节意识比技术含量更珍贵。
第二,MySQL建表脚本写得干净利落。每个表都有注释,每个字段都有COMMENT,索引设计合理,字符集统一utf8mb4。这套脚本本身就是很好的建表范本,值得抄下来在自己的项目里照着用。
第三,前后端数据流链路清晰,新手上手很容易。从Controller到Service到Mapper,再到前端的API封装和页面渲染,整个链路没有花哨的东西,每一步在哪里看什么文件一目了然。这样的项目风格,是项目可维护性的隐形财富。
最后再分享一个调试小技巧:前后端联调时如果发现数据不对,不要急着改代码,先看Network面板里接口请求和响应的完整报文。大部分问题在数据链路上,而不是逻辑上。数据通了,一切好说;数据不通,改哪里都是瞎改。
这套系统做完之后,往小了说是有了一套能用的车辆管理工具,往大了说,它是学习中小企业全栈项目开发全流程的一份很好的“活教材”。如果你正在找工作,别光把这个项目写在简历上,用一天时间把它改造成一个属于你自己的版本——换个行业场景、加几个业务字段、写一套自己的界面,面试官看到的就不只是一个“别人的项目”,而是一个“能干活的人”。
