每到期末,Java课程设计、毕业设计、甚至是大作业,都让无数同学感到头疼。时间紧、任务重,既要理解SpringBoot、Vue这些框架,又要设计数据库、实现前后端交互,最后还要写出一份像样的文档。很多人从零开始,往往卡在环境配置、跨域问题、接口联调这些“坑”里,一周过去了,项目还没跑起来。
这篇文章要解决的,就是这个问题。我们不谈空洞的理论,直接给你一个能跑通、能理解、能二次开发的 SpringBoot + Vue2 前后端分离超市管理系统 。它不是一个简单的“Hello World”,而是一个包含了用户管理、商品分类、库存、销售等核心业务模块的完整项目。更重要的是,我会带你 在1小时内完成从环境准备到项目成功运行的完整流程 ,并提供清晰的源码、数据库脚本和项目文档。
这篇文章的价值在于: 它是一份“可运行的参考答案”和“高效的脚手架” 。对于急需完成作业的同学,你可以直接部署运行,理解业务逻辑后快速完成自己的任务。对于想深入学习SpringBoot和Vue前后端分离开发的同学,这个项目结构清晰、代码规范,是绝佳的练手案例。我们将避开那些教程里语焉不详的“坑”,把重点放在“如何让项目跑起来”和“核心代码如何工作”上。
1. 这篇文章真正要解决的问题:从“无从下手”到“一小时跑通”
很多同学在拿到“开发一个管理系统”的任务时,面临的第一个困境不是编码,而是 如何开始 。技术选型(SpringBoot还是SSM?)、项目结构(前后端分离怎么组织?)、环境搭建(Node.js、Maven、MySQL版本冲突)、基础功能(登录拦截、权限校验、分页查询)……每一个环节都可能消耗大量时间。
这个超市管理系统项目,就是为了跨越这个“启动成本”而设计的。它预设了最主流、最稳定的技术栈(SpringBoot 2.x + Vue2 + Element UI + MyBatis-Plus),实现了管理系统最常见的增删改查、登录鉴权、数据可视化等需求。你不需要从零搭建这些基础框架,而是可以直接在成熟的项目骨架上,专注于你的业务逻辑实现或学习其设计思想。
具体来说,读完本文并实践后,你将能解决以下问题:
- 环境与依赖 :快速配置好Java、Maven、Node.js、MySQL环境,并解决常见的版本兼容问题。
- 项目启动 :在1小时内,成功在本地运行起一个功能完整的前后端分离项目。
- 核心流程理解 :掌握从前端Vue页面发起请求,到后端SpringBoot控制器接收、服务层处理、MyBatis-Plus操作数据库,再返回数据给前端的完整数据流。
- 二次开发 :知道如何在这个项目基础上,修改页面、添加新的数据表和业务接口。
- 避坑指南 :提前了解并解决“端口冲突”、“跨域问题”、“数据库连接失败”、“前端打包部署”等高频错误。
2. 项目核心架构与技术栈选型解析
在动手之前,我们需要理解这个项目的“骨架”。一个清晰的技术架构图,能让你在后续的编码和调试中,清楚地知道每一行代码属于哪个层次,起到了什么作用。
本项目采用经典的 前后端分离架构 ,前端和后端完全独立开发、部署和运行,通过HTTP API(主要是RESTful风格)进行通信。
用户浏览器
|
| (访问 http://localhost:8080)
v
[Nginx] (可选,用于生产环境部署或解决开发跨域)
|
| (代理API请求到后端)
v
[Vue2前端项目] (运行在Node.js服务上,端口如:8080)
| |
| | (静态资源)
| v
| 用户浏览器
|
| (Ajax请求,如 /api/user/login)
v
[SpringBoot后端项目] (运行在Java服务上,端口如:9090)
|
| (调用Service, 操作数据库)
v
[MyBatis-Plus]
|
v
[MySQL数据库]
技术栈明细与选型理由:
| 层级 | 技术 | 版本(建议) | 选型理由与在项目中的作用 |
|---|---|---|---|
| 前端 | Vue.js | 2.x | 渐进式框架,生态成熟,学习曲线平缓,适合快速开发管理后台。 |
| Element UI | 2.x | 基于Vue的桌面端组件库,提供了丰富的表格、表单、弹窗等组件,极大提升开发效率。 | |
| Axios | 1.x | 基于Promise的HTTP库,用于前端向后端发送请求、拦截请求和响应。 | |
| Vue Router | 3.x | 官方路由管理器,实现前端单页面应用(SPA)的页面跳转。 | |
| Vuex (可选) | 3.x | 状态管理模式,用于管理跨组件共享的状态(如用户登录信息)。 | |
| 后端 | SpringBoot | 2.7.x | 简化Spring初始搭建和开发过程,内嵌Tomcat,开箱即用。 |
| MyBatis-Plus | 3.5.x | MyBatis的增强工具,提供了通用的Mapper和Service,无需编写简单SQL,支持Lambda查询。 | |
| MySQL | 5.7 / 8.0 | 最流行的开源关系型数据库,用于存储业务数据。 | |
| Lombok | 1.18.x | 通过注解自动生成getter/setter等方法,减少模板代码。 | |
| Hutool | 5.x | 国产Java工具类库,提供字符串、日期、加密等常用工具方法。 | |
| JWT | 3.x | JSON Web Token,用于实现无状态的用户认证与授权。 | |
| 构建与依赖 | Maven | 3.6+ | Java项目管理和构建自动化工具。 |
| npm / yarn | 任意 | Node.js包管理工具,用于管理前端依赖。 |
为什么是Vue2而不是Vue3? 对于课程设计、毕业设计或快速开发场景,稳定性和生态完备性优先。Vue2拥有更广泛的教学资源、社区问答和第三方组件库支持(如Element UI对Vue2的支持最成熟),能确保你在遇到问题时能快速找到解决方案。Vue3虽然是趋势,但其组合式API和生态迁移会为新手带来额外的学习成本。本项目聚焦于“快速实现”,故选择Vue2。
3. 一小时极速部署:环境准备与项目启动
我们的目标是“一小时跑通”,因此环境准备必须高效、无坑。请严格按照以下步骤操作。
3.1 基础环境检查清单
在开始之前,请确保你的电脑上已经安装了以下软件,并确认版本基本符合要求。
-
Java JDK
:版本 8 或 11(推荐11)。打开命令行,输入
java -version检查。 -
Maven
:版本 3.6+。命令行输入
mvn -v检查。 -
Node.js
:版本 14+ 或 16+(推荐16 LTS)。命令行输入
node -v和npm -v检查。 - MySQL :版本 5.7 或 8.0。确保MySQL服务已启动,并记住root用户的密码。
- IDE :后端推荐使用 IntelliJ IDEA (社区版或旗舰版),前端可使用 VS Code 或 IDEA 内的插件。本文以IDEA和VS Code为例。
- Git (可选):用于克隆项目源码。
3.2 获取项目源码与数据库脚本
假设你已经从提供的链接下载了项目压缩包,其目录结构应大致如下:
supermarket-management/
├── backend/ # SpringBoot后端项目
│ ├── src/
│ ├── pom.xml
│ └── ...
├── frontend/ # Vue2前端项目
│ ├── public/
│ ├── src/
│ ├── package.json
│ └── ...
└── docs/ # 项目文档、数据库脚本等
├── database.sql # MySQL数据库初始化脚本
├── 需求文档.md
└── 部署说明.md
第一步:导入并初始化数据库
- 打开MySQL客户端(如命令行、Navicat、MySQL Workbench)。
-
创建一个新的数据库,例如命名为
supermarket_db,字符集选择utf8mb4,排序规则选择utf8mb4_general_ci。 -
执行
docs/database.sql文件中的所有SQL语句。这将创建所有数据表(如user,product,category,order等)并插入必要的初始数据(如管理员账号)。
-- 这是一个示例性的SQL片段,实际文件会更完整
CREATE DATABASE IF NOT EXISTS `supermarket_db` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE `supermarket_db`;
CREATE TABLE `sys_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码',
`role` varchar(20) DEFAULT 'USER' COMMENT '角色',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表';
INSERT INTO `sys_user` (`username`, `password`, `role`) VALUES ('admin', '加密后的密码', 'ADMIN');
3.3 后端SpringBoot项目配置与启动
-
用IDEA打开后端项目
:打开IntelliJ IDEA,选择
File->Open,找到并选择backend文件夹,点击确定。IDEA会自动识别为Maven项目并开始下载依赖(请保持网络畅通)。 -
修改数据库配置
:找到
backend/src/main/resources/application.yml(或application.properties) 文件。
# application.yml 示例配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/supermarket_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root # 改为你的MySQL用户名
password: 123456 # 改为你的MySQL密码
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB
# MyBatis-Plus 配置
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除字段名
logic-delete-value: 1 # 逻辑已删除值
logic-not-delete-value: 0 # 逻辑未删除值
configuration:
map-underscore-to-camel-case: true # 自动驼峰映射
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印SQL,调试用,生产环境关闭
# 应用配置
server:
port: 9090 # 后端服务端口
servlet:
context-path: /api # API统一前缀
# JWT配置
jwt:
secret: your-secret-key-here-change-in-production # JWT密钥,生产环境务必修改!
expire: 604800 # token有效期(秒),7天
关键点 :
-
将
url、username、password修改为你本地MySQL的实际信息。 -
server.port定义了后端服务端口(9090),前端将通过这个端口访问API。 -
jwt.secret是令牌签名密钥, 在正式部署时,必须修改为一个复杂的随机字符串 。
-
启动后端项目
:在IDEA中找到主启动类(通常命名为
XxxApplication.java,例如SupermarketApplication.java),右键点击,选择Run ‘SupermarketApplication’。 -
验证启动成功
:观察控制台日志,如果没有报错,最后出现类似
Started SupermarketApplication in 5.123 seconds (JVM running for 6.456)的日志,并在浏览器中访问http://localhost:9090/api(或项目内定义的某个测试接口,如/api/hello),如果能看到响应,说明后端启动成功。
3.4 前端Vue项目配置与启动
-
用VS Code打开前端项目
:打开VS Code,选择
File->Open Folder,找到并选择frontend文件夹。 -
安装依赖
:在VS Code中打开终端(Terminal),确保路径在前端项目根目录下,运行以下命令。
如果网络较慢,可以使用淘宝镜像
npm config set registry https://registry.npmmirror.com。
npm install
# 或使用 yarn
# yarn install
这个过程会下载
package.json
中定义的所有依赖包(如vue, element-ui, axios等),可能会花费几分钟。
-
配置后端API地址
:前端需要知道后端服务在哪里。找到前端项目中配置axios全局请求地址的文件,通常是
src/utils/request.js或src/api/axios.js。
// src/utils/request.js 示例
import axios from 'axios'
// 创建axios实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API || 'http://localhost:9090/api', // 后端API基础地址
timeout: 15000 // 请求超时时间
})
// ... 请求拦截器、响应拦截器等配置
export default service
或者,更常见的做法是在项目根目录的
.env.development
文件中配置:
# .env.development
VUE_APP_BASE_API = http://localhost:9090/api
确保这里的端口(9090)和后端
application.yml
中配置的
server.port
一致。
- 启动前端开发服务器 :在终端中运行以下命令。
npm run serve
# 或使用 yarn
# yarn serve
成功启动后,终端会显示类似如下信息:
App running at:
- Local: http://localhost:8080/
- Network: http://192.168.x.x:8080/
-
访问系统
:打开浏览器,访问
http://localhost:8080。你应该能看到系统的登录页面。
至此,前后端项目均已启动成功。 你可以使用数据库脚本中初始化的账号(如 admin/123456)进行登录,开始探索系统功能。
4. 核心功能模块与代码实现拆解
项目跑起来了,但更重要的是理解代码。我们选取几个最核心的模块,看看前后端是如何协同工作的。
4.1 用户登录与JWT认证流程
这是任何管理系统的入口。流程如下:
- 前端:用户在登录页输入用户名密码,点击登录。
-
前端:通过axios发送POST请求到
/api/auth/login。 -
后端:控制器(
AuthController)接收请求,调用服务层验证用户名密码。 - 后端:验证通过后,使用JWT工具生成一个Token(包含用户ID、角色等信息)。
- 后端:将Token返回给前端。
-
前端:收到Token后,将其存储到本地(如localStorage或Vuex),并在后续的每次请求头中携带(
Authorization: Bearer <token>)。 -
后端:通过拦截器(
JwtInterceptor)验证每个请求的Token是否有效、是否过期,并从中解析出用户信息,供业务层使用。
后端关键代码示例:
// 1. 登录请求DTO (Data Transfer Object)
@Data
public class LoginDTO {
private String username;
private String password;
}
// 2. 认证服务层接口
@Service
public class AuthService {
@Autowired
private UserMapper userMapper;
@Autowired
private JwtUtil jwtUtil; // 自定义的JWT工具类
public String login(LoginDTO loginDTO) {
// 查询用户
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername, loginDTO.getUsername());
User user = userMapper.selectOne(wrapper);
// 验证用户是否存在及密码是否正确 (密码需加密存储,这里简化)
if (user == null || !user.getPassword().equals(encryptPassword(loginDTO.getPassword()))) {
throw new BusinessException("用户名或密码错误");
}
// 生成JWT Token
Map<String, String> claims = new HashMap<>();
claims.put("userId", String.valueOf(user.getId()));
claims.put("role", user.getRole());
return jwtUtil.generateToken(claims);
}
}
// 3. 认证控制器
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private AuthService authService;
@PostMapping("/login")
public Result<String> login(@RequestBody LoginDTO loginDTO) {
String token = authService.login(loginDTO);
return Result.success(token);
}
}
// 4. JWT请求拦截器 (简化版)
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("Authorization");
if (StringUtils.isBlank(token) || !token.startsWith("Bearer ")) {
throw new UnauthorizedException("请先登录");
}
token = token.substring(7); // 去掉 "Bearer "
// 验证并解析token
Claims claims = jwtUtil.parseToken(token);
// 将用户信息存入请求上下文,方便后续使用
request.setAttribute("USER_ID", claims.get("userId"));
request.setAttribute("USER_ROLE", claims.get("role"));
return true;
}
}
前端关键代码示例:
// src/api/auth.js - 封装登录API
import request from '@/utils/request'
export function login(data) {
return request({
url: '/auth/login',
method: 'post',
data
})
}
// src/views/login.vue - 登录组件方法
export default {
data() {
return {
loginForm: {
username: '',
password: ''
}
}
},
methods: {
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
// 调用登录API
login(this.loginForm).then(response => {
const token = response.data
// 存储token到本地存储和Vuex
this.$store.dispatch('user/setToken', token)
// 将token设置到axios请求头
setToken(token)
// 跳转到首页
this.$router.push('/')
}).catch(error => {
this.$message.error(error.message || '登录失败')
})
}
})
}
}
}
4.2 商品管理模块的增删改查(CRUD)
这是业务系统的核心。我们以商品管理为例,展示如何使用MyBatis-Plus快速实现CRUD。
后端实现:
- 实体类 (Entity) :对应数据库表。
@Data
@TableName("product") // 指定表名
public class Product {
@TableId(type = IdType.AUTO) // 主键自增
private Long id;
private String name;
private String category;
private BigDecimal price;
private Integer stock;
private String description;
private String imageUrl;
@TableField(fill = FieldFill.INSERT) // 插入时自动填充
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) // 插入和更新时自动填充
private LocalDateTime updateTime;
}
-
Mapper接口
:继承MyBatis-Plus的
BaseMapper,即可获得基础CRUD方法。
@Mapper // 或在启动类加 @MapperScan
public interface ProductMapper extends BaseMapper<Product> {
// 可以在此定义自定义的复杂SQL方法
// 例如分页查询关联分类名称
IPage<ProductVO> selectProductPage(Page<ProductVO> page, @Param("query") ProductQuery query);
}
-
服务层接口与实现
:通常我们会有一个接口和一个实现类,但使用MyBatis-Plus的
IService和ServiceImpl可以更简化。
public interface ProductService extends IService<Product> {
// 声明业务方法,如分页查询
PageResult<ProductVO> getProductPage(ProductQuery query);
}
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
@Override
public PageResult<ProductVO> getProductPage(ProductQuery query) {
// 构建分页参数
Page<ProductVO> page = new Page<>(query.getPageNum(), query.getPageSize());
// 调用Mapper自定义的分页查询方法
IPage<ProductVO> result = baseMapper.selectProductPage(page, query);
return new PageResult<>(result.getRecords(), result.getTotal());
}
}
- 控制器 (Controller) :接收前端请求,调用服务层。
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
// 分页查询
@GetMapping("/page")
public Result<PageResult<ProductVO>> getPage(ProductQuery query) {
return Result.success(productService.getProductPage(query));
}
// 新增商品
@PostMapping
public Result<Void> addProduct(@RequestBody @Valid ProductDTO productDTO) {
Product product = new Product();
BeanUtils.copyProperties(productDTO, product);
productService.save(product);
return Result.success();
}
// 更新商品
@PutMapping("/{id}")
public Result<Void> updateProduct(@PathVariable Long id, @RequestBody @Valid ProductDTO productDTO) {
Product product = new Product();
BeanUtils.copyProperties(productDTO, product);
product.setId(id);
productService.updateById(product);
return Result.success();
}
// 删除商品(逻辑删除)
@DeleteMapping("/{id}")
public Result<Void> deleteProduct(@PathVariable Long id) {
productService.removeById(id); // 由于配置了逻辑删除,这里实际是更新deleted字段
return Result.success();
}
}
前端页面组件 (Vue + Element UI):
<template>
<div class="product-container">
<!-- 搜索和操作栏 -->
<el-form :inline="true" :model="queryParams">
<el-form-item label="商品名称">
<el-input v-model="queryParams.name" placeholder="请输入" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">搜索</el-button>
<el-button @click="resetQuery">重置</el-button>
<el-button type="success" @click="handleAdd">新增</el-button>
</el-form-item>
</el-form>
<!-- 商品表格 -->
<el-table :data="productList" border style="width: 100%">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="商品名称" />
<el-table-column prop="category" label="分类" />
<el-table-column prop="price" label="价格" />
<el-table-column prop="stock" label="库存" />
<el-table-column label="操作" width="200">
<template slot-scope="scope">
<el-button size="mini" @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-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 50]"
:page-size="queryParams.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
<!-- 新增/编辑对话框 -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible">
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="商品名称" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<!-- 其他表单字段 -->
</el-form>
<div slot="footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getProductPage, addProduct, updateProduct, deleteProduct } from '@/api/product'
export default {
name: 'ProductManagement',
data() {
return {
queryParams: { pageNum: 1, pageSize: 10, name: '' },
productList: [],
total: 0,
dialogVisible: false,
dialogTitle: '',
form: { id: null, name: '', category: '', price: 0, stock: 0 },
rules: { name: [{ required: true, message: '请输入商品名称', trigger: 'blur' }] }
}
},
created() {
this.getList()
},
methods: {
// 获取分页列表
getList() {
getProductPage(this.queryParams).then(response => {
this.productList = response.data.list
this.total = response.data.total
})
},
// 处理搜索
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
// 处理新增
handleAdd() {
this.dialogTitle = '新增商品'
this.form = { id: null, name: '', category: '', price: 0, stock: 0 }
this.dialogVisible = true
},
// 处理编辑
handleEdit(row) {
this.dialogTitle = '编辑商品'
this.form = { ...row } // 浅拷贝行数据到表单
this.dialogVisible = true
},
// 提交表单(新增或更新)
submitForm() {
this.$refs.form.validate(valid => {
if (valid) {
const api = this.form.id ? updateProduct : addProduct
api(this.form).then(() => {
this.$message.success('操作成功')
this.dialogVisible = false
this.getList() // 刷新列表
})
}
})
},
// 处理删除
handleDelete(row) {
this.$confirm('确认删除该商品?', '提示', { type: 'warning' }).then(() => {
deleteProduct(row.id).then(() => {
this.$message.success('删除成功')
this.getList()
})
})
}
// ... 其他方法(重置、分页事件等)
}
}
</script>
通过以上代码,一个完整的商品管理CRUD功能就实现了。前端通过Axios调用后端的RESTful API,后端使用MyBatis-Plus高效操作数据库,并通过统一的
Result
对象和异常处理器返回标准化的JSON数据。
5. 项目打包与部署(生产环境准备)
开发环境运行良好后,你可能需要将项目打包,部署到服务器或提交给老师。这里介绍两种常见的部署方式。
5.1 后端SpringBoot项目打包
SpringBoot项目通常打包成一个可执行的JAR文件,内嵌了Tomcat服务器。
-
使用Maven打包
:在IDEA的Maven工具栏中,双击
package命令,或在项目根目录下执行命令。
cd backend
mvn clean package -DskipTests
-DskipTests
参数会跳过单元测试,加快打包速度。
-
找到JAR包 :打包成功后,在
backend/target/目录下会生成一个xxx-0.0.1-SNAPSHOT.jar文件(名称取决于pom.xml中的<artifactId>和<version>)。 -
运行JAR包 :将JAR包上传到服务器(或直接在本地测试),确保该环境已安装Java运行环境(JRE),然后运行:
java -jar your-project-name.jar
你可以通过
--server.port=9091
参数来指定运行端口,或使用外部的
application-prod.yml
配置文件。
5.2 前端Vue项目打包
Vue项目需要打包成静态文件(HTML, CSS, JS),然后由Nginx等Web服务器托管。
-
修改生产环境API地址
:在项目根目录创建或修改
.env.production文件。
# .env.production
VUE_APP_BASE_API = /api
这里设置为
/api
,意味着前端请求会发向同域名下的
/api
路径,这需要部署时由Nginx将
/api
的请求代理到后端真正的服务地址。
- 执行打包命令 :
cd frontend
npm run build
# 或使用 yarn
# yarn build
打包成功后,会在项目根目录下生成一个
dist
文件夹,里面就是所有的静态资源。
5.3 使用Nginx进行前后端整合部署(推荐)
在生产环境,我们通常使用Nginx同时作为静态资源服务器和反向代理服务器。
- 安装Nginx :在服务器上安装Nginx。
-
配置Nginx
:编辑Nginx的配置文件(如
/etc/nginx/conf.d/supermarket.conf)。
server {
listen 80;
server_name your-domain.com; # 你的域名或IP
# 前端静态资源
location / {
root /path/to/your/frontend/dist; # 前端dist目录的绝对路径
index index.html;
try_files $uri $uri/ /index.html; # 支持Vue Router的history模式
}
# 反向代理后端API请求
location /api/ {
proxy_pass http://localhost:9090/api/; # 代理到后端SpringBoot服务
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
- 重启Nginx :
sudo nginx -s reload
这样,用户访问
http://your-domain.com
就能看到前端页面,而前端发往
/api
的请求会被Nginx转发到后端的
http://localhost:9090
,完美解决了跨域问题。
6. 常见问题与排查思路(避坑指南)
在实际运行中,你可能会遇到以下问题。这里提供快速的排查思路。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
前端访问
localhost:8080
白屏或报错
|
1. 前端依赖未安装成功。
2.
npm run serve
命令执行目录不对。
3. Node.js版本不兼容。 |
1. 检查终端是否有错误,重新
npm install
。
2. 确认终端路径在
frontend
目录下。
3. 检查
package.json
中的
engines
字段或尝试切换Node版本。
|
删除
node_modules
和
package-lock.json
,重新
npm install
。使用
nvm
管理Node版本。
|
| 前端登录后,接口请求返回404或跨域错误 |
1. 后端服务未启动。
2. 前端配置的API地址(
baseURL
)错误。
3. 后端端口被占用。 |
1. 访问
http://localhost:9090/api/hello
(或你的测试接口) 看后端是否正常。
2. 检查前端
request.js
或
.env
文件中的
baseURL
。
3. 查看后端启动日志。 |
1. 确保后端服务在运行。
2. 修正前端API配置,确保端口一致。 3. 修改后端
application.yml
中的
server.port
。
|
| 后端启动时报数据库连接错误 |
1. MySQL服务未启动。
2.
application.yml
中数据库配置错误(IP、端口、库名、用户名、密码)。
3. MySQL版本驱动不匹配。 |
1. 检查MySQL服务状态。
2. 逐项核对配置文件。 3. 查看错误日志,确认驱动类名(
com.mysql.cj.jdbc.Driver
用于MySQL 8+)。
|
1. 启动MySQL服务。
2. 使用命令行或工具测试连接。 3. 检查pom.xml中MySQL驱动版本,8.x用
mysql-connector-java
,5.x用
mysql-connector-java
5.x版本。
|
| 后端启动时报“端口被占用” | 已有进程占用了9090端口。 | 在命令行执行 `netstat -ano |
findstr :9090
(Windows) 或
lsof -i:9090` (Mac/Linux) 查找进程ID并结束它。
|
| 页面能打开,但表格数据不显示 |
1. 后端分页查询接口有误。
2. 前端请求参数格式不对。 3. 浏览器控制台有JS错误或网络请求失败。 |
1. 打开浏览器开发者工具(F12)的“网络(Network)”标签,查看API请求的响应状态和内容。
2. 查看后端控制台日志,确认接口是否被调用及SQL是否执行。 |
1. 根据网络请求的响应调试后端接口。
2. 检查前端调用API的代码,确保参数正确传递。 |
| 执行删除操作后,数据还在 | 可能配置了MyBatis-Plus的逻辑删除,但前端表格未刷新或查询条件未包含逻辑删除字段。 |
1. 检查数据库对应记录的
deleted
字段是否已被更新为1。
2. 检查后端查询SQL是否自动添加了
deleted=0
条件。
|
确保后端查询方法继承自MyBatis-Plus的
BaseMapper
或
ServiceImpl
,它们会自动处理逻辑删除条件。前端在删除成功后调用刷新列表的方法。
|
| 打包后部署到服务器,前端页面空白或资源404 |
1. Nginx配置的root路径错误。
2. Vue Router使用了history模式,但Nginx未配置
try_files
。
3. 静态资源路径错误(如使用了绝对路径)。 |
1. 检查Nginx配置中的
root
指令路径是否正确指向
dist
目录。
2. 检查Nginx配置中是否有
try_files $uri $uri/ /index.html;
。
3. 检查前端
vue.config.js
中是否配置了正确的
publicPath
。
|
1. 修正Nginx配置路径。
2. 确保Nginx配置支持history模式。 3. 在前端
vue.config.js
中设置
publicPath: './'
或根据部署目录调整。
|
7. 项目扩展与二次开发建议
这个超市管理系统是一个基础模板,你可以基于它进行扩展,使其更符合你的课程设计或毕业设计要求。
-
添加新模块 :例如“供应商管理”、“会员管理”、“采购入库单管理”。
-
后端
:复制商品模块的代码结构,创建新的
Entity,Mapper,Service,Controller,修改对应的表名和字段。 -
前端
:在
src/views下新建一个Vue组件,模仿商品管理的页面和交互,调用新的API。
-
后端
:复制商品模块的代码结构,创建新的
-
增强业务逻辑 :
-
销售出库
:在销售商品时,需要同步减少库存。这需要在
OrderService中实现事务管理,确保扣减库存和创建订单要么同时成功,要么同时失败。 -
数据校验
:在DTO类中使用JSR-303注解(如
@NotBlank,@Min,@Max)进行更严格的输入校验。 - 操作日志 :使用Spring AOP或拦截器,记录用户的关键操作(登录、增删改)到日志表。
-
销售出库
:在销售商品时,需要同步减少库存。这需要在
-
优化用户体验 :
- 数据导出 :集成EasyExcel或Apache POI,实现将商品列表、销售报表导出为Excel文件。
- 图表展示 :集成ECharts,在仪表盘页面展示销售趋势、商品分类占比等图表。
- 权限细化 :目前的角色可能只有ADMIN和USER。你可以引入RBAC(基于角色的访问控制)模型,实现更细粒度的菜单权限和按钮权限控制。
-
代码优化 :
-
统一响应封装
:确保所有控制器都返回
Result<T>对象。 -
全局异常处理
:使用
@ControllerAdvice和@ExceptionHandler捕获并处理各类异常,返回友好的错误信息。 -
参数验证
:在Controller方法参数上使用
@Valid注解,并在实体类字段上使用校验注解。
-
统一响应封装
:确保所有控制器都返回
通过这个项目,你不仅得到了一个可以交差的作业,更获得了一个理解现代Java Web开发技术栈(SpringBoot, Vue, MyBatis-Plus)的绝佳实践样本。建议你在成功运行的基础上,尝试着去修改它、扩展它,这个过程本身,就是最有价值的学习。

284

被折叠的 条评论
为什么被折叠?



