做Java后端的人,十有八九都接手过这类管理系统项目。最近不少朋友在找基于Java和Vue的课程设计或毕业设计案例,点名要电池销售系统源码加数据库加文档,我才意识到这套选题的覆盖面比想象中大得多。今天先不卖关子,直接把这类项目的完整拆解思路和实操细节聊透,从技术选型到数据库设计,从后端接口到前端联调,再到部署运行和踩坑记录,一篇讲完。不管是拿去做课设、写简历项目,还是单纯想学Spring Boot和Vue的组合玩法,这篇文章都能给你一套可以直接抄作业的底稿。
这个系统表面上是个“电池销售系统”,但你把它拆开看,其实就是一个标准的进销存管理原型,核心业务围绕电池的“入库—销售—库存变动”这条链路展开。具备的功能无非是电池信息管理、客户管理、销售订单创建、库存扣减与预警、销售统计报表,外加一个登录认证的后台。听着不复杂,但要做到数据一致、流程闭环、页面顺手,该考虑的细节一个都少不了。
1. 项目全貌与技术选型:这个电池销售系统到底在做什么
1.1 业务需求界定:为什么拿“电池”做业务载体
你可能会想,做一个“电池销售系统”和做一个“图书管理系统”“超市收银系统”,底层逻辑不是差不多吗?确实差不多,但电池这个品类有它独特的属性维度,很适合做教学型项目。电池有明确的品牌、型号、规格、电压、容量、适用场景等参数,字段丰富但不复杂,既能体现数据建模的完整度,又不会让初学者一头雾水。更重要的一点是,电池属于标准工业品,SKU(库存量单位)明确,非常适合做库存管理和订单关联的演示。
从用户角色上看,这个系统至少要区分两种身份:管理员和销售员。管理员负责维护电池基础信息、查看整体销售报表、管理客户资料;销售员主要使用销售下单功能、查询订单、查询库存。这种角色划分在权限管理上可以做得简单,用一张user表加一个role字段就能解决,不需要引入Spring Security这类重量级框架。对于课设和简历项目来说,控制复杂度是一门必修课,别一上来就整微服务、Redis缓存、消息队列,那是给自己挖坑。
业务闭环是这类系统的核心价值。一个合格的电池销售系统,从创建订单开始,就应该自动完成库存扣减、库存流水记录、订单总额计算,最后能在报表统计里体现出来。很多人的课设项目只是机械地做了增删改查,缺少这种“业务联动”的设计感,面试官一眼就看穿。
1.2 技术栈选型思路:Spring Boot + Vue这一套为什么主流
后端选Java,基本就是在Spring Boot和更传统的SSM框架之间做选择。现在行业里Spring Boot已经是绝对的主流,内嵌Tomcat容器、自动配置机制、起步依赖管理,省掉了大量繁琐的XML配置。一个单体应用管理系统,用Spring Boot搭建的成本极低,几分钟就能跑起来一个能用的Web工程。如果你拿到的是传统的SSM项目,也不代表不能用,但如果是新开项目,我建议直接用Spring Boot,网上资料和现成框架实在太多,遇到问题能搜到大量解决方案。
持久层方面,MyBatis在课设项目里的出场率远高于Spring Data JPA。原因不难理解:MyBatis写SQL直观可控,出了性能问题可以直接看日志里的SQL语句,调试难度低;而且中文学习资料、面试八股文里,MyBatis的占比一直居高不下。你以后出去面试Java开发岗,MyBatis是躲不掉的话题,在课设项目里提前用一遍比临时抱佛脚背面试题强得多。数据库用MySQL,这是国内Java项目的默认标配,没有争议。
前端选择Vue,核心原因在于它的上手曲线比React平缓,模板语法直观,中文社区活跃,而且Element UI/Element Plus这个组件库对后台管理系统的支持实在太成熟了——表格、分页、弹窗、表单校验,都开箱即用。我见过不少只用了一天Vue官方文档就跟着模板把管理系统页面撸出来的同学,换成React大概没这么顺。至于选Vue 2还是Vue 3,关键看你拿到的项目模板是什么版本。如果从零开始,现在我会直接上Vue 3 + Element Plus + Vite;如果已有Vue 2 + Element UI的现成架子,那也不必推翻,先把业务跑通更重要。
前端的路由用Vue Router,状态管理如果项目规模不大,其实用不上Vuex或者Pinia,组件内维护状态、路由传参就够用了。很多人一上来就在前端项目里铺一个store文件夹,结果状态没几个,代码复杂度倒是翻倍。先想清楚要解决的问题,再决定要不要上状态管理库,这个习惯在项目工程化中非常重要。
1.3 部署形态与认证方案:前后端分离到底怎么配合
这个系统按前后端分离来做,后端跑在8081端口提供JSON接口,前端跑在5173或8080端口,通过代理转发请求,这是当前Java + Vue项目最常见的工程形态。前后端分离带来的一个直接问题就是跨域和登录状态管理。
登录认证我会优先考虑JWT。为什么不用传统的Session?因为前后端分离后,如果前端和后端部署在不同域名或端口,Session的Cookie跨域处理很麻烦,要配置复杂的跨域支持。JWT把用户身份信息加密后放在token里,前端存localStorage,每次请求在请求头里带Authorization字段,后端通过拦截器校验token,这套模式的跨域成本最低,也好理解。
不过JWT有一个天然的短板:token在到期前无法主动作废。所以在实际项目中,如果用户点退出登录,前端直接把本地token删掉,后端是无感的。好在管理系统项目的安全要求没那么苛刻,不必为了这个缺陷引入Redis来做token黑名单。如果你想把这块做细一点,可以在后端维护一张token失效表,或者在数据库里加一个token版本号字段,但对我个人而言,课设和简历项目的场景下,没必要。
2. 核心模块拆解与数据库设计:6张表把业务串起来
2.1 功能模块划分:从登录到报表的完整闭环
一个完整的电池销售系统,至少应该包含以下几个功能模块。首先是登录认证模块,用户输入账号密码,后端校验通过后返回token,前端跳转到首页;其次是电池信息管理,对电池的品牌、型号、规格、电压、容量、单价、库存进行增删改查,支持分页和关键字搜索;然后是客户管理,维护客户的基本资料,方便下单时直接选择客户。
再往下是销售订单模块,这是整个项目的核心。用户选择客户,从电池列表里挑选电池并填入数量,生成订单明细,系统自动计算总金额,确认下单后扣减库存并生成库存流水。最后是统计报表模块,展示今日销售额、总订单数、低库存预警电池、月度销售趋势、热销电池排行等数据。这个模块在课设答辩里往往是加分项,有数据图表比纯表格直观得多。
权限控制上可以做得轻量:登录接口返回的token里包含用户角色,前端在路由守卫里根据角色判断哪些页面可以访问,后端在拦截器里校验接口的权限要求。比如销售员不允许访问用户管理相关的接口。这一步做一半也行,但既然区分了管理员和销售员,至少要塞一个能看懂的权限判断逻辑进去。
2.2 数据库表设计:核心表结构和字段说明
数据库设计直接决定整个项目的开发效率。我建议按6张核心表来建模:user(用户表)、battery(电池信息表)、customer(客户表)、sale_order(销售订单主表)、sale_order_item(销售订单明细表)、stock_record(库存流水表)。
battery表是业务的主体,字段设计要够用但别冗余。我给出一个基础的建表语句,关键字段都有注释,你拿到后直接执行就行:
CREATE TABLE `battery` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', `battery_name` varchar(50) NOT NULL COMMENT '电池名称', `brand` varchar(50) DEFAULT NULL COMMENT '品牌', `model` varchar(50) DEFAULT NULL COMMENT '型号', `specification` varchar(100) DEFAULT NULL COMMENT '规格', `voltage` decimal(5,1) DEFAULT NULL COMMENT '电压(V)', `capacity` int DEFAULT NULL COMMENT '容量(mAh)', `unit_price` decimal(10,2) NOT NULL COMMENT '销售单价', `stock` int NOT NULL DEFAULT '0' COMMENT '当前库存', `stock_threshold` int DEFAULT '10' COMMENT '库存预警阈值', `status` tinyint DEFAULT '1' COMMENT '状态 1启用 0停用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_brand` (`brand`), KEY `idx_battery_name` (`battery_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='电池信息表';订单主表和明细表拆成两张,是为了支持一个订单包含多种电池的场景,这种1:N的设计是订单系统的基本功。订单主表存客户、总金额、状态、操作人、备注、创建时间,明细表存每个电池的购买数量和当时的单价。
stock_record库存流水表很多人忽略,但我建议一定要有。有了它,你可以追溯每一次库存变动的来源:哪个订单、谁操作的、之前多少、之后多少。一旦数据对不上,查流水表能迅速定位问题。
关于外键,我建议全部用逻辑外键而不是物理外键。物理外键在数据库层面加约束,看似严谨,实际开发中会遇到一大堆麻烦:批量导入数据困难、删除关联数据被阻塞、运维改数据要小心翼翼。主流互联网公司的数据库设计基本都放弃物理外键,靠应用层保证数据一致性。课设项目里不加物理外键,只要在代码里做好关联校验,答辩时还有得聊。
2.3 关键业务规则:销售下单的事务与库存扣减
销售下单是整个系统最值得深挖的业务逻辑。我来梳理一下完整流程:前端提交订单数据(客户ID、订单明细列表),后端先生成唯一订单号,插入sale_order主记录,再遍历明细列表,逐条插入sale_order_item,同时扣减battery表对应记录的库存,最后写入stock_record流水,累加总金额。
这段逻辑必须放在一个事务里,用@Transactional注解包裹。为什么?想象一下:订单包含两种电池,第一种库存够,扣减成功,第二种库存不足,抛出异常,这时如果不做事务回滚,数据库里就残留了一条只有第一种电池的订单记录,而且第一种电池的库存已经被莫名其妙扣掉了,数据就脏了。事务的原子性正是解决这种“要么全部成功,要么全部失败”的场景。
扣减库存时不能简单地先select查出库存,判断是否大于0,再update扣减。在高并发场景下,两个请求同时读到库存为10,同时扣减8,结果库存变成-6,这就是超卖。解决方式很简单,用一条条件更新SQL:
UPDATE battery SET stock = stock - #{quantity} WHERE id = #{id} AND stock >= #{quantity}这条SQL的巧妙之处在于,数据库行锁会保证同一时刻只有一个事务能更新这条记录,并且stock >= #{quantity}这个条件确保了库存不会被扣成负数。如果更新影响的行数为0,说明库存不足,直接抛异常回滚事务。对于课设项目这个方案完全够用,还顺手解决了并发问题,面试聊起来也是加分点。
2.4 报表统计SQL怎么写才像样
统计报表是很多同学不知道从哪里下手的地方。其实核心就是几条带GROUP BY的SQL。比如统计最近6个月的每月销售趋势:
SELECT DATE_FORMAT(create_time, '%Y-%m') AS month, COUNT(*) AS order_count, SUM(total_amount) AS total_sales FROM sale_order WHERE order_status = 1 AND create_time >= DATE_SUB(NOW(), INTERVAL 6 MONTH) GROUP BY DATE_FORMAT(create_time, '%Y-%m') ORDER BY month DESC;再比如热销电池排行,需要把订单明细表和电池表关联起来,按电池分组求和:
SELECT b.battery_name, b.brand, SUM(oi.quantity) AS sale_quantity FROM sale_order_item oi JOIN battery b ON oi.battery_id = b.id JOIN sale_order o ON oi.order_id = o.id WHERE o.order_status = 1 GROUP BY oi.battery_id, b.battery_name, b.brand ORDER BY sale_quantity DESC LIMIT 10;这两条SQL覆盖了“按时间聚合”和“关联多表聚合”两类典型场景,能写出来,说明你对SQL的掌握已经超过大部分只会增删改查的人了。前端拿到查询结果后,用ECharts画柱状图和饼图,视觉效果立刻提升一个档次。
3. 后端和前端的关键实现:从接口到页面的完整闭环
3.1 后端目录结构与分层规范
拿到一套Java后端源码,先看目录结构。规范的目录结构是项目可维护性的第一道保障。我这里给出一个简洁但分层清晰的结构:
com.example.battery ├── controller // 接口层,只做参数接收和结果返回 ├── service // 业务层,核心逻辑 │ └── impl ├── mapper // MyBatis数据访问层 ├── entity // 数据库实体 ├── dto // 请求参数对象 ├── vo // 响应视图对象 ├── config // 配置类(跨域、拦截器、WebMvc) ├── common // 通用类(Result、异常、常量、工具) └── BatteryApplication.javacontroller层保持薄,只做三件事:接收前端参数、调用service方法、返回统一结果。业务判断都放在service层,这种习惯要从小项目养起。我见过不少把SQL写进Controller的课设代码,运行起来没问题,但代码没法看,答辩时老师看一眼就想让你重新设计。
统一返回结果类Result是前后端联调的基石。设计成三个字段:code(200成功、401未认证、500业务异常)、message(提示信息)、data(业务数据)。前端axios响应拦截器统一判断code,省得每个页面都写一遍错误处理。
3.2 登录认证与拦截器细节
登录流程的代码实现并不复杂,但有几个坑必须提。后端定时器校验JWT的拦截器需要排除登录接口,不然用户没登录就被拦下来了。更关键的是要放行OPTIONS请求。前端在跨域场景下会先发一个OPTIONS预检请求,如果拦截器直接把OPTIONS请求拦了,前端就会报跨域错误。很多同学被这个坑折磨一整天,其实就是拦截器没放行OPTIONS。
另外,跨域配置文件里,要对allowedHeaders做明确配置。前端带了Authorization请求头,如果后端跨域配置里没有允许这个头,浏览器还是会拦截响应。较新版本的Spring Boot推荐用allowedOriginPatterns而不是allowedOrigins,后者在配合allowedHeaders时容易出现“Cannot allow credentials for origin”这类报错,这是个典型的版本差异问题。
前端侧的登录路由守卫也要处理好。Vue Router的beforeEach钩子里,读取localStorage中的token,没有token就重定向到/login。这里有个细节,很多人的路由守卫判断逻辑写错了,导致用户刷新页面后明明token还在,却被踢回登录页。通常是因为在守卫里用了异步获取token的方式,而localStorage是同步API,直接读就行,千万别绕弯子。
3.3 axios封装:统一请求入口的必要性
前端直接在每个组件里调axios并不可取,重复代码多不说,统一改超时时间、统一做错误提示时你会崩溃。正确的做法是对axios做二次封装,我给出一个精简版:
import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截器:自动携带token request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) // 响应拦截器:统一处理业务码和HTTP错误 request.interceptors.response.use( res => { if (res.data.code === 200) { return res.data.data } ElMessage.error(res.data.message || '请求失败') return Promise.reject(new Error(res.data.message)) }, err => { if (err.response && err.response.status === 401) { localStorage.removeItem('token') router.push('/login') } ElMessage.error(err.message || '网络异常') return Promise.reject(err) } ) export default request这段封装的妙处在于:所有接口的调用方只关心业务数据本身,不用重复处理token携带和错误弹窗。后端返回code非200时统一弹错误提示,HTTP 401统一跳登录页。这才是项目级的请求处理方式。数据库增删改查的接口调用,在有了这一层封装后,各个页面的代码会干净很多。
3.4 电池列表页与销售下单页的实现思路
电池列表页是管理系统最典型的页面。搜索栏放电池名称和品牌两个输入框,加一个搜索按钮;主区域用el-table展示数据,库存列做一个高亮判断,stock字段小于stock_threshold时显示为红色标签;表格右上角放“新增”按钮,行内放“编辑”“删除”。这个页面的核心逻辑是分页查询接口的对接,前端向后端传pageNum、pageSize、keyword,后端返回total和records列表。用Element Plus的el-pagination组件,注意页码切换和搜索条件重置这两个细节。
销售下单页稍微复杂一点。左侧是一张可搜索的电池列表,展示库存和单价,每条记录带“加入订单”按钮;右侧是已选电池的明细区域,可以修改数量、删除行,自动联动小计和总金额的实时计算;最下方是客户选择下拉框和“提交订单”按钮。提交前要做前端校验:客户不能为空、明细列表不能为空、数量不能为0。确认后调用后端创建订单接口,成功后清空已选明细,跳转到订单列表页。
这个页面用到的Vue知识点比较密集:响应式数据的更新、计算属性做金额汇总、事件处理、组件间数据传递、路由跳转,是一次非常全面的Vue练习。路由参数传递在页面跳转时也用得上,例如从订单列表跳转到订单详情时,通过query传订单ID,详情页拿到ID后请求接口拉数据。
3.5 前端菜单权限怎么处理
如果你想让项目在答辩时更有亮点,可以做一个简单的菜单权限。管理员登录后侧边栏显示“电池管理、客户管理、订单管理、统计报表、用户管理”五个菜单,销售员登录后只显示前四个,把“用户管理”隐藏掉。
实现方式不复杂:登录接口返回的data里带userInfo对象,里面包含role字段;前端在登录成功后把userInfo存到localStorage;Layout组件里的侧边栏菜单通过一个计算属性,根据当前角色过滤掉不可见的菜单项。后端在用户管理相关的Controller上加权限校验,比如管理员操作接口时校验token中解析出来的角色是admin,否则返回401。这套方案虽然简单,但把“前后端都做了权限控制”这一点讲清楚,面试官会认可你的工程意识。
4. 环境搭建与部署实操:从零把项目跑起来
4.1 本地开发环境准备清单
拿到源码包之后,第一步不是打开IDE,而是先确认环境。我习惯按下面的顺序核对:JDK版本、Maven版本、MySQL版本、Node.js版本。Spring Boot 2.x通常基于JDK 8或11,Spring Boot 3.x必须JDK 17+。先打开后端pom.xml看parent标签里的版本,心里有数。前端则要看package.json里vue和element-plus的版本,Vue 3项目通常配Node.js 16以上。
数据库部分,用Navicat或MySQL Workbench执行项目里的SQL脚本。如果脚本里没有建库语句,需要先手动建库,指定utf8mb4字符集,再导入脚本:
CREATE DATABASE battery_sales DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE battery_sales;导入后先别急着跑,用SELECT语句简单查看几张表的数据量,确认表结构和初始数据都对。这一步能帮你避免后端启动后因为表不存在或字段对不上而报一堆奇怪的错误。
4.2 后端启动:改配置、装依赖、跑起来
后端启动的常规操作是:IDEA直接打开后端根目录的pom.xml,识别为Maven项目后会自动下载依赖。下载过程可能持续几分钟,如果你所在的网络环境访问Maven中央仓库不够快,可以配置阿里云镜像。在本地Maven的settings.xml里加mirror配置,下载速度会提升很多。
然后修改application.yml里的数据库连接信息:
server: port: 8081 spring: datasource: url: jdbc:mysql://localhost:3306/battery_sales?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.DriverMySQL连接url里加characterEncoding=utf8是避免中文乱码的关键,serverTimezone=Asia/Shanghai是解决时区问题。如果你的MySQL是8.x,驱动类名必須是com.mysql.cj.jdbc.Driver;如果是MySQL 5.7,用com.mysql.jdbc.Driver也行,但建议统一换成cj驱动,兼容性更好。
启动主类BatteryApplication,看到Spring Boot的启动日志和Tomcat started on port 8081,说明后端已就绪。接着用Postman或者浏览器直接访问登录接口测试,确认能返回token,再继续往下走。这一步必须做,否则当前端联调出错时,你都不知道问题在后端还是前端。
4.3 前端启动:npm安装和代理配置
前端项目解压后,先看有没有node_modules目录,没有的话执行npm install。国内网络环境下,建议先把npm registry切换到淘宝镜像:
npm config set registry https://registry.npmmirror.com npm install依赖安装完成后执行npm run dev。Vite启动的默认端口是5173,如果被占用需要修改vite.config.js里的server.port。接下来是前后端联调的关键——配置代理。开发环境下,前端和后端端口不同,直接请求会遇到跨域问题。最省心的方式是在vite.config.js里配置代理:
server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8081', changeOrigin: true } } }这样前端代码里的axios请求,比如POST /api/auth/login,会被Vite开发服务器转发到后端8081端口,浏览器看到的请求是同源的,彻底避开跨域问题。生产部署时,则通过nginx做同样的反向代理配置。
4.4 数据库初始数据准备
项目自带的SQL脚本里通常有一段初始数据:管理员账号(admin/123456)、销售员账号、一批电池样例数据、几个客户、若干条销售订单。这些数据的意义不仅是让页面打开有内容可看,更是你熟悉系统业务逻辑的抓手。
建议你把初始数据过一遍,重点看订单表和明细表的数据,理解一个订单对应多行明细的关系。必要时自己手工执行几条INSERT语句,比如新增一个电池、创建一条订单,再查询库存流水表,观察数据变化。这个操作能帮助你在调试时快速定位问题出在哪个环节。
5. 常见问题与避坑实录:这些坑我基本都踩过
5.1 高频报错速查表
我从实际辅导和排查的经验里,整理了这张高频报错对照表,遇到问题直接按表排查:
| 现象 | 根因 | 解决方案 |
|---|---|---|
| 前端请求接口一直404 | 代理配置没生效或后端上下文路径不对 | 检查vite.config.js的proxy配置和后端server.servlet.context-path |
| 登录后刷新页面又跳登录页 | 路由守卫没有正确读取持久化token | 确认localStorage里token有效,且守卫逻辑是同步读取 |
| 下单提示库存不足但页面显示有货 | 扣库存SQL没加stock >= quantity条件 | 使用条件更新SQL,影响行数为0时抛异常 |
| 跨域报错CORS | 后端没配置跨域,或前端没用代理 | 优先使用代理方案,后端cors配置兜底 |
| 中文乱码 | 数据库连接url缺characterEncoding | 在jdbcUrl末尾加characterEncoding=utf8 |
| 数据库导入失败 | SQL脚本带建库语句且已存在同名库 | 先删除旧库,或手动CREATE DATABASE后导表 |
| 启动报Driver类找不到 | MySQL版本与驱动类不匹配 | MySQL 8.x用com.mysql.cj.jdbc.Driver |
5.2 删除电池数据的安全处理
删除电池信息时,一定先判断这张电池是否已经被订单引用过。如果没有物理外键,数据库层面不会拦你,但删掉之后关联查询就查不到电池名称了,页面会出现一堆空字段,数据完整性被破坏。
推荐做法是逻辑删除而不是物理删除。在battery表加status字段,1表示启用,0表示停用。删除操作实际是UPDATE status = 0,查询时默认过滤掉status为0的记录。这样既能从列表隐藏,又保留了历史订单的关联数据。如果坚持物理删除,操作前必须用COUNT语句检查sale_order_item表里是否存在关联记录,存在就拒绝删除并提示“该电池已有历史订单,不可删除”。
5.3 精度、时间和枚举这几个细节
金额字段在Java里必须用BigDecimal,千万别用double或float。经典问题0.1 + 0.2在二进制浮点数里会产生0.30000000000000004这样的结果,放到订单总额里就是事故。数据库层面decimal(10,2),Java实体类用BigDecimal,前端显示时用toFixed(2)格式化,全程不碰浮点类型,这是处理金额的正确姿势。
时间字段统一用datetime类型,Java实体类用LocalDateTime。很多老项目还在用java.util.Date,新项目建议直接LocalDateTime,配合Jackson的格式化注解或者全局配置,序列化成前端期望的字符串格式。前端显示时间时用dayjs或直接截取字符串,避免出现“2024-01-01T00:00:00”这种英文格式。
订单状态这类有固定取值的字段,建议用数字常量加注释,或者定义一个枚举类。0待付款、1已完成、2已取消,比用字符串灵活,也方便SQL里写条件判断。答辩时能说出“我用枚举统一管理了订单状态码”,也能体现代码规范意识。
5.4 前后端联调排查顺序
前后端联调出现问题时,很多人会慌,一上来就怀疑前端代码有bug,然后疯狂改前端。正确的排查顺序我说过无数次,再重复一遍:先用Postman或curl直接调用后端接口,确认返回结果正确,再回头查前端。如果后端接口就有问题,那一定是后端的问题,改前端永远绕不过去。
用Postman调试时,注意请求头的Authorization值,必须在前面加“Bearer ”前缀(注意空格),这是JWT认证的标准格式。很多人token明明有效,却因为格式不对被后端拦截,报401,白白排查半天。
前端排查时,打开浏览器开发者工具的Network面板,看请求是否发出、请求头是否正确、响应体是什么。Vue项目的调试信息一般会打印在控制台,Element Plus的报错信息也会给出中英文提示。学会看控制台是前端开发的基本功,这一步跑不掉。
5.5 从课设到简历项目:还能怎么升华
如果你打算把这个项目写进简历,光说“我开发了一个电池销售系统”是没有竞争力的。面试官对这个项目不感兴趣,他感兴趣的是你在项目里解决过什么问题、做过什么设计决策。几个可以深挖的亮点值得自己主动准备:
第一是并发扣库存的条件更新SQL方案,这是正儿八经的并发问题解决方案;第二是数据库表设计的逻辑外键和库存流水追溯设计,说明你有数据一致性意识;第三是JWT认证和路由守卫的前后端权限控制方案,说明你对前后端分离架构有完整的认知。这三个点讲清楚,项目在面试官眼里的技术含量至少上升一个档次。
时间充裕的话,可以顺手补两个功能:用EasyExcel或POI导出订单列表为Excel文件,把统计报表用ECharts渲染成图表。这两个功能实现成本不高,但简历写“批量导出”“数据可视化”时就有了真实依据,比空泛地写“熟悉Vue、熟悉Spring Boot”可信得多。
5.6 项目还能往哪扩展
如果后续还想继续完善这个系统,我建议优先加一个进货管理和供应商表。电池销售不只是出库,还需要进货入库,有进有出才构成完整的进销存闭环。进货时增加库存、生成进货流水,和销售出库形成对称结构,系统的业务完整性会大大提升。
另一个值得加的是订单状态流转。目前订单可能只有一个完成状态,可以扩展成待付款、已付款、已发货、已完成、已取消的完整状态机,并在前端让操作按钮根据状态显示或隐藏。这个需求贴近真实业务,做完之后你对状态机设计的理解会比背任何八股文都深刻。
再加一个库存预警通知功能也很有意思。用一个定时任务扫描battery表,发现stock小于stock_threshold的记录,就生成一条预警消息或发送邮件。Spring Boot自带的@Scheduled注解就能实现,复杂度不高,效果却很明显,属于性价比很高的扩展功能。
最后再分享一个我个人的实操习惯:拿到任何一套“源码+数据库+文档”的项目,不要急着双击运行,先看README,再看SQL脚本,理清表结构,然后浏览后端接口列表,最后才看前端页面。按这个顺序过一遍,两个小时之内你就能把这个系统的业务逻辑摸透,后面的调试效率至少翻倍。如果没有文档,那就从数据库入手,表结构就是最好的业务说明书。
这套Java + Vue的电池销售系统,说白了就是一份浓缩版的业务管理系统开发范本。它最大的价值不在于“电池”这个业务本身,而在于它把一个典型管理系统的所有关键环节都串了一遍:登录认证、权限控制、增删改查、分页搜索、事务处理、关联查询、统计报表、前后端分离部署。把这些环节逐个吃透,你的Java全栈能力就稳了一大截。