一小时部署SpringBoot+Vue2超市管理系统:从零到可运行的完整实战

每到期末,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),实现了管理系统最常见的增删改查、登录鉴权、数据可视化等需求。你不需要从零搭建这些基础框架,而是可以直接在成熟的项目骨架上,专注于你的业务逻辑实现或学习其设计思想。

具体来说,读完本文并实践后,你将能解决以下问题:

  1. 环境与依赖 :快速配置好Java、Maven、Node.js、MySQL环境,并解决常见的版本兼容问题。
  2. 项目启动 :在1小时内,成功在本地运行起一个功能完整的前后端分离项目。
  3. 核心流程理解 :掌握从前端Vue页面发起请求,到后端SpringBoot控制器接收、服务层处理、MyBatis-Plus操作数据库,再返回数据给前端的完整数据流。
  4. 二次开发 :知道如何在这个项目基础上,修改页面、添加新的数据表和业务接口。
  5. 避坑指南 :提前了解并解决“端口冲突”、“跨域问题”、“数据库连接失败”、“前端打包部署”等高频错误。

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 基础环境检查清单

在开始之前,请确保你的电脑上已经安装了以下软件,并确认版本基本符合要求。

  1. Java JDK :版本 8 或 11(推荐11)。打开命令行,输入 java -version 检查。
  2. Maven :版本 3.6+。命令行输入 mvn -v 检查。
  3. Node.js :版本 14+ 或 16+(推荐16 LTS)。命令行输入 node -v npm -v 检查。
  4. MySQL :版本 5.7 或 8.0。确保MySQL服务已启动,并记住root用户的密码。
  5. IDE :后端推荐使用 IntelliJ IDEA (社区版或旗舰版),前端可使用 VS Code 或 IDEA 内的插件。本文以IDEA和VS Code为例。
  6. Git (可选):用于克隆项目源码。

3.2 获取项目源码与数据库脚本

假设你已经从提供的链接下载了项目压缩包,其目录结构应大致如下:

supermarket-management/
├── backend/          # SpringBoot后端项目
│   ├── src/
│   ├── pom.xml
│   └── ...
├── frontend/         # Vue2前端项目
│   ├── public/
│   ├── src/
│   ├── package.json
│   └── ...
└── docs/             # 项目文档、数据库脚本等
    ├── database.sql  # MySQL数据库初始化脚本
    ├── 需求文档.md
    └── 部署说明.md

第一步:导入并初始化数据库

  1. 打开MySQL客户端(如命令行、Navicat、MySQL Workbench)。
  2. 创建一个新的数据库,例如命名为 supermarket_db ,字符集选择 utf8mb4 ,排序规则选择 utf8mb4_general_ci
  3. 执行 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项目配置与启动

  1. 用IDEA打开后端项目 :打开IntelliJ IDEA,选择 File -> Open ,找到并选择 backend 文件夹,点击确定。IDEA会自动识别为Maven项目并开始下载依赖(请保持网络畅通)。
  2. 修改数据库配置 :找到 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 是令牌签名密钥, 在正式部署时,必须修改为一个复杂的随机字符串
  1. 启动后端项目 :在IDEA中找到主启动类(通常命名为 XxxApplication.java ,例如 SupermarketApplication.java ),右键点击,选择 Run ‘SupermarketApplication’
  2. 验证启动成功 :观察控制台日志,如果没有报错,最后出现类似 Started SupermarketApplication in 5.123 seconds (JVM running for 6.456) 的日志,并在浏览器中访问 http://localhost:9090/api (或项目内定义的某个测试接口,如 /api/hello ),如果能看到响应,说明后端启动成功。

3.4 前端Vue项目配置与启动

  1. 用VS Code打开前端项目 :打开VS Code,选择 File -> Open Folder ,找到并选择 frontend 文件夹。
  2. 安装依赖 :在VS Code中打开终端(Terminal),确保路径在前端项目根目录下,运行以下命令。 如果网络较慢,可以使用淘宝镜像 npm config set registry https://registry.npmmirror.com
npm install
# 或使用 yarn
# yarn install

这个过程会下载 package.json 中定义的所有依赖包(如vue, element-ui, axios等),可能会花费几分钟。

  1. 配置后端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 一致。

  1. 启动前端开发服务器 :在终端中运行以下命令。
npm run serve
# 或使用 yarn
# yarn serve

成功启动后,终端会显示类似如下信息:

App running at:
- Local:   http://localhost:8080/
- Network: http://192.168.x.x:8080/
  1. 访问系统 :打开浏览器,访问 http://localhost:8080 。你应该能看到系统的登录页面。

至此,前后端项目均已启动成功。 你可以使用数据库脚本中初始化的账号(如 admin/123456)进行登录,开始探索系统功能。

4. 核心功能模块与代码实现拆解

项目跑起来了,但更重要的是理解代码。我们选取几个最核心的模块,看看前后端是如何协同工作的。

4.1 用户登录与JWT认证流程

这是任何管理系统的入口。流程如下:

  1. 前端:用户在登录页输入用户名密码,点击登录。
  2. 前端:通过axios发送POST请求到 /api/auth/login
  3. 后端:控制器( AuthController )接收请求,调用服务层验证用户名密码。
  4. 后端:验证通过后,使用JWT工具生成一个Token(包含用户ID、角色等信息)。
  5. 后端:将Token返回给前端。
  6. 前端:收到Token后,将其存储到本地(如localStorage或Vuex),并在后续的每次请求头中携带( Authorization: Bearer <token> )。
  7. 后端:通过拦截器( 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。

后端实现:

  1. 实体类 (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;
}
  1. Mapper接口 :继承MyBatis-Plus的 BaseMapper ,即可获得基础CRUD方法。
@Mapper // 或在启动类加 @MapperScan
public interface ProductMapper extends BaseMapper<Product> {
    // 可以在此定义自定义的复杂SQL方法
    // 例如分页查询关联分类名称
    IPage<ProductVO> selectProductPage(Page<ProductVO> page, @Param("query") ProductQuery query);
}
  1. 服务层接口与实现 :通常我们会有一个接口和一个实现类,但使用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());
    }
}
  1. 控制器 (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服务器。

  1. 使用Maven打包 :在IDEA的Maven工具栏中,双击 package 命令,或在项目根目录下执行命令。
cd backend
mvn clean package -DskipTests

-DskipTests 参数会跳过单元测试,加快打包速度。

  1. 找到JAR包 :打包成功后,在 backend/target/ 目录下会生成一个 xxx-0.0.1-SNAPSHOT.jar 文件(名称取决于pom.xml中的 <artifactId> <version> )。

  2. 运行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服务器托管。

  1. 修改生产环境API地址 :在项目根目录创建或修改 .env.production 文件。
# .env.production
VUE_APP_BASE_API = /api

这里设置为 /api ,意味着前端请求会发向同域名下的 /api 路径,这需要部署时由Nginx将 /api 的请求代理到后端真正的服务地址。

  1. 执行打包命令
cd frontend
npm run build
# 或使用 yarn
# yarn build

打包成功后,会在项目根目录下生成一个 dist 文件夹,里面就是所有的静态资源。

5.3 使用Nginx进行前后端整合部署(推荐)

在生产环境,我们通常使用Nginx同时作为静态资源服务器和反向代理服务器。

  1. 安装Nginx :在服务器上安装Nginx。
  2. 配置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;
    }
}
  1. 重启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. 项目扩展与二次开发建议

这个超市管理系统是一个基础模板,你可以基于它进行扩展,使其更符合你的课程设计或毕业设计要求。

  1. 添加新模块 :例如“供应商管理”、“会员管理”、“采购入库单管理”。

    • 后端 :复制商品模块的代码结构,创建新的 Entity , Mapper , Service , Controller ,修改对应的表名和字段。
    • 前端 :在 src/views 下新建一个Vue组件,模仿商品管理的页面和交互,调用新的API。
  2. 增强业务逻辑

    • 销售出库 :在销售商品时,需要同步减少库存。这需要在 OrderService 中实现事务管理,确保扣减库存和创建订单要么同时成功,要么同时失败。
    • 数据校验 :在DTO类中使用JSR-303注解(如 @NotBlank , @Min , @Max )进行更严格的输入校验。
    • 操作日志 :使用Spring AOP或拦截器,记录用户的关键操作(登录、增删改)到日志表。
  3. 优化用户体验

    • 数据导出 :集成EasyExcel或Apache POI,实现将商品列表、销售报表导出为Excel文件。
    • 图表展示 :集成ECharts,在仪表盘页面展示销售趋势、商品分类占比等图表。
    • 权限细化 :目前的角色可能只有ADMIN和USER。你可以引入RBAC(基于角色的访问控制)模型,实现更细粒度的菜单权限和按钮权限控制。
  4. 代码优化

    • 统一响应封装 :确保所有控制器都返回 Result<T> 对象。
    • 全局异常处理 :使用 @ControllerAdvice @ExceptionHandler 捕获并处理各类异常,返回友好的错误信息。
    • 参数验证 :在Controller方法参数上使用 @Valid 注解,并在实体类字段上使用校验注解。

通过这个项目,你不仅得到了一个可以交差的作业,更获得了一个理解现代Java Web开发技术栈(SpringBoot, Vue, MyBatis-Plus)的绝佳实践样本。建议你在成功运行的基础上,尝试着去修改它、扩展它,这个过程本身,就是最有价值的学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值