当前位置: 首页 > news >正文

用Vue 2.7 + SpringBoot 3.1 + MySQL 8 从零搭建一个超市商品管理系统(附完整源码和数据库脚本)

Vue 2.7 + SpringBoot 3.1 + MySQL 8 全栈开发超市商品管理系统实战

超市商品管理系统是零售行业数字化转型的典型应用场景。对于计算机专业学生和全栈开发初学者而言,通过一个完整的项目实践来掌握前后端技术栈的协同开发,是提升工程能力的有效途径。本文将基于Vue 2.7、SpringBoot 3.1和MySQL 8技术组合,从零开始构建一个功能完备的超市商品管理系统,重点解决实际开发中的技术难点和常见陷阱。

1. 开发环境准备与项目初始化

1.1 开发工具配置

工欲善其事,必先利其器。在开始编码前,我们需要确保开发环境配置正确:

  • Java开发环境:JDK 17(SpringBoot 3.1的最低要求)
  • Node.js环境:建议安装LTS版本(如16.x)
  • IDE选择
    • IntelliJ IDEA(后端开发)
    • VS Code(前端开发)
  • 数据库工具:MySQL Workbench或Navicat

注意:SpringBoot 3.1要求JDK 17及以上版本,与SpringBoot 2.x系列有显著差异,这是初学者容易忽略的兼容性问题。

1.2 项目骨架搭建

我们采用前后端分离的架构,需要分别初始化两个项目:

后端项目初始化(SpringBoot 3.1)

# 使用Spring Initializr创建项目 curl https://start.spring.io/starter.zip \ -d dependencies=web,mybatis-plus,mysql,lombok \ -d javaVersion=17 \ -d artifactId=supermarket-backend \ -d bootVersion=3.1.0 \ -o supermarket-backend.zip

前端项目初始化(Vue 2.7)

# 使用Vue CLI创建项目 npm install -g @vue/cli vue create supermarket-frontend # 选择Vue 2模板,手动配置添加Router和Vuex

1.3 数据库环境准备

MySQL 8相比5.7版本有显著的性能提升和安全增强,我们需要特别注意认证方式的配置:

-- 创建数据库和用户 CREATE DATABASE supermarket CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'superuser'@'%' IDENTIFIED WITH mysql_native_password BY 'SecurePass123!'; GRANT ALL PRIVILEGES ON supermarket.* TO 'superuser'@'%'; FLUSH PRIVILEGES;

2. 数据库设计与MyBatis Plus集成

2.1 数据库表结构设计

超市管理系统的核心实体包括商品、商品类型、超市区域和货架。以下是优化后的DDL:

CREATE TABLE `product_category` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL COMMENT '分类名称', `sort_order` int DEFAULT '0' COMMENT '排序字段', `status` tinyint DEFAULT '1' COMMENT '状态:1-启用,0-禁用', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品分类表'; CREATE TABLE `store_area` ( `id` bigint NOT NULL AUTO_INCREMENT, `area_code` varchar(20) NOT NULL COMMENT '区域编码', `area_name` varchar(50) NOT NULL COMMENT '区域名称', `description` varchar(200) DEFAULT NULL, `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_area_code` (`area_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='超市区域表';

2.2 MyBatis Plus 3.5.3.1配置技巧

MyBatis Plus能极大简化DAO层开发,但在SpringBoot 3.1中需要特别注意以下配置:

# application.yml配置 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: db-config: id-type: auto logic-delete-field: deleted # 逻辑删除字段 logic-not-delete-value: 0 logic-delete-value: 1

实体类示例:

@Data @TableName("product") public class Product { @TableId(type = IdType.AUTO) private Long id; private String name; private String barcode; private BigDecimal price; private Integer stock; private Long categoryId; private Long shelfId; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; }

3. 后端API开发实战

3.1 RESTful API设计规范

我们遵循行业通用的RESTful设计原则:

  • 资源命名:使用复数名词(如/products)
  • HTTP方法
    • GET:获取资源
    • POST:创建资源
    • PUT:全量更新
    • PATCH:部分更新
    • DELETE:删除资源
  • 状态码
    • 200:成功
    • 201:创建成功
    • 400:客户端错误
    • 404:资源不存在
    • 500:服务器错误

3.2 商品管理API实现

商品管理是系统的核心模块,我们采用三层架构:

  1. Controller层:处理HTTP请求
@RestController @RequestMapping("/api/products") @RequiredArgsConstructor public class ProductController { private final ProductService productService; @GetMapping public ResponseEntity<PageResult<ProductVO>> listProducts( @RequestParam(required = false) String name, @RequestParam(required = false) Long categoryId, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size) { return ResponseEntity.ok(productService.listProducts(name, categoryId, page, size)); } }
  1. Service层:业务逻辑处理
@Service @RequiredArgsConstructor public class ProductServiceImpl implements ProductService { private final ProductMapper productMapper; @Override @Transactional(readOnly = true) public PageResult<ProductVO> listProducts(String name, Long categoryId, Integer page, Integer size) { Page<Product> pageInfo = new Page<>(page, size); LambdaQueryWrapper<Product> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.like(StringUtils.isNotBlank(name), Product::getName, name) .eq(categoryId != null, Product::getCategoryId, categoryId); Page<Product> productPage = productMapper.selectPage(pageInfo, queryWrapper); return new PageResult<>(productPage.getTotal(), productPage.getRecords().stream().map(this::convertToVO).collect(Collectors.toList())); } }
  1. Mapper层:数据持久化
public interface ProductMapper extends BaseMapper<Product> { @Select("SELECT p.*, c.name as category_name FROM product p LEFT JOIN product_category c ON p.category_id = c.id WHERE p.id = #{id}") ProductDetailVO selectProductDetailById(@Param("id") Long id); }

3.3 全局异常处理

统一的异常处理能提升API的健壮性:

@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) { return ResponseEntity.status(ex.getStatus()) .body(new ErrorResponse(ex.getCode(), ex.getMessage())); } @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) { String message = ex.getBindingResult().getFieldErrors().stream() .map(FieldError::getDefaultMessage) .collect(Collectors.joining(", ")); return ResponseEntity.badRequest() .body(new ErrorResponse("VALIDATION_FAILED", message)); } }

4. 前端开发与前后端联调

4.1 Vue项目结构优化

合理的项目结构能提高代码可维护性:

src/ ├── api/ # API请求封装 ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── router/ # 路由配置 ├── store/ # Vuex状态管理 ├── utils/ # 工具函数 ├── views/ # 页面组件 │ ├── product/ # 商品管理相关页面 │ ├── category/ # 分类管理 │ └── layout/ # 布局组件 └── main.js # 应用入口

4.2 Axios封装与API调用

对Axios进行二次封装,统一处理请求和响应:

// api/request.js import axios from 'axios' const service = axios.create({ baseURL: process.env.VUE_APP_API_BASE_URL, timeout: 10000 }) // 请求拦截器 service.interceptors.request.use( config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = `Bearer ${token}` } return config }, error => { return Promise.reject(error) } ) // 响应拦截器 service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { Message.error(res.message || 'Error') return Promise.reject(new Error(res.message || 'Error')) } return res.data }, error => { Message.error(error.message || '请求失败') return Promise.reject(error) } ) export default service

4.3 商品列表页面实现

使用Element UI实现商品管理界面:

<template> <div class="product-container"> <el-card shadow="never"> <div class="filter-container"> <el-input v-model="listQuery.name" placeholder="商品名称" style="width: 200px" @keyup.enter.native="handleFilter" /> <el-select v-model="listQuery.categoryId" placeholder="商品分类" clearable style="width: 200px" > <el-option v-for="item in categories" :key="item.id" :label="item.name" :value="item.id" /> </el-select> <el-button type="primary" @click="handleFilter">查询</el-button> </div> <el-table v-loading="listLoading" :data="list" border fit highlight-current-row > <el-table-column label="ID" prop="id" width="80" /> <el-table-column label="商品名称" prop="name" /> <el-table-column label="分类" prop="categoryName" /> <el-table-column label="价格" prop="price" /> <el-table-column label="库存" prop="stock" /> <el-table-column label="操作" width="200"> <template #default="{row}"> <el-button size="mini" @click="handleEdit(row)">编辑</el-button> <el-button size="mini" type="danger" @click="handleDelete(row)" >删除</el-button > </template> </el-table-column> </el-table> <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" /> </el-card> </div> </template> <script> import { fetchList, deleteProduct } from '@/api/product' import Pagination from '@/components/Pagination' export default { name: 'ProductList', components: { Pagination }, data() { return { list: null, total: 0, listLoading: true, listQuery: { page: 1, limit: 10, name: undefined, categoryId: undefined }, categories: [] } }, created() { this.getList() this.getCategories() }, methods: { getList() { this.listLoading = true fetchList(this.listQuery).then(response => { this.list = response.items this.total = response.total this.listLoading = false }) }, handleFilter() { this.listQuery.page = 1 this.getList() }, handleEdit(row) { this.$router.push(`/product/edit/${row.id}`) }, handleDelete(row) { this.$confirm('确认删除该商品?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { deleteProduct(row.id).then(() => { this.$message.success('删除成功') this.getList() }) }) } } } </script>

5. 系统部署与性能优化

5.1 后端部署配置

SpringBoot应用的生产环境配置要点:

# application-prod.yml server: port: 8080 compression: enabled: true mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json min-response-size: 1024 spring: datasource: url: jdbc:mysql://prod-db:3306/supermarket?useSSL=false&characterEncoding=utf8 username: ${DB_USER} password: ${DB_PASSWORD} hikari: maximum-pool-size: 20 connection-timeout: 30000 jpa: open-in-view: false cache: type: redis redis: time-to-live: 3600000 management: endpoints: web: exposure: include: health,info,metrics

5.2 前端性能优化

通过以下手段提升Vue应用性能:

  1. 路由懒加载
const ProductList = () => import('./views/product/List.vue') const ProductEdit = () => import('./views/product/Edit.vue')
  1. 代码分割:在vue.config.js中配置
module.exports = { configureWebpack: { optimization: { splitChunks: { chunks: 'all', maxSize: 244 * 1024 // 244KB } } } }
  1. CDN引入常用库
// vue.config.js module.exports = { chainWebpack: config => { config.externals({ vue: 'Vue', 'vue-router': 'VueRouter', axios: 'axios', 'element-ui': 'ELEMENT' }) } }

5.3 数据库性能调优

针对超市管理系统的高频查询场景,我们需要优化数据库:

  1. 索引优化
-- 为高频查询字段添加索引 ALTER TABLE product ADD INDEX idx_category_status (category_id, status); ALTER TABLE product ADD INDEX idx_name (name);
  1. 查询优化
// 使用MyBatis Plus的selectPage优化分页查询 public Page<Product> listProducts(int page, int size) { return productMapper.selectPage(new Page<>(page, size), Wrappers.<Product>lambdaQuery() .select(Product.class, info -> !info.getColumn().equals("description") && !info.getColumn().equals("specification")) ); }
  1. 缓存策略
// 使用Spring Cache注解实现缓存 @Cacheable(value = "product", key = "#id", unless = "#result == null") public Product getProductById(Long id) { return productMapper.selectById(id); } @CacheEvict(value = "product", key = "#product.id") public void updateProduct(Product product) { productMapper.updateById(product); }
http://www.cnnetsun.cn/news/2082864.html

相关文章:

  • GitHub Copilot SDK实战:从API调用到智能代码审查助手开发
  • GitHub 一夜爆火!这个项目解决了 AI 编程最大的坑:没有记忆
  • 抖音下载神器:如何一键批量保存无水印视频和直播回放?
  • Vivado 2023.2版本实战:从“Labtools 27-3303”到“Place 30-602”,一次解决时钟与烧录难题
  • Redis怎样利用Lua脚本批量抓取多类型数据
  • copyKAT实战:从单细胞转录组数据自动识别肿瘤细胞CNV与亚克隆结构
  • 告别AC5!在Keil MDK AC6环境下为STM32配置串口打印(Retarget详解)
  • 别再踩坑了!Docker容器里用systemctl报错Failed to get D-Bus connection的完整解决手册
  • League Akari终极指南:英雄联盟本地自动化工具完整使用教程
  • 别再只盯着USB和HDMI了!聊聊LVDS这个‘老将’为什么在工业屏和医疗设备里依然能打
  • 3个魔法步骤:让Windows 11完美运行20年前的经典游戏
  • 从零开始:如何快速掌握Switch大气层系统1.7.1完整安装指南
  • 基于scikit-learn的轻量级手语识别系统实践
  • 如何在Windows 11上完美运行DirectX 1-7经典游戏:DDrawCompat终极兼容方案
  • Zotero PDF Translate:你的学术翻译助手到底有多强大?
  • 给Linux小白的模块管理指南:从插U盘到装显卡驱动,都离不开modprobe
  • SpringAI实战:基于MCP协议的AI Agent工具链集成指南
  • 3个智能功能彻底改变你的英雄联盟游戏体验
  • STM32H7的MPU实战:用内存保护单元给你的代码加把锁,防止数组越界和野指针
  • 终极B站会员购抢票指南:biliTickerBuy帮你告别手速焦虑![特殊字符]
  • 【无人机】固定翼无人机简化燃油燃烧仿真的模拟模型(Matlab代码实现)
  • [Rust][RISCV] 一、用 Rust 写 RISC-V BootROM —— 你需要知道的 Rust 基础
  • 抖音内容批量下载实战:从单视频到直播回放的完整解决方案
  • E-Hentai批量下载终极指南:如何一键保存整个漫画图库
  • 当你的STM32项目有10个IIC设备,8个地址还一样?试试这个C语言‘面向对象’的软件IIC驱动方案
  • 小红书下载器终极教程:3分钟上手批量下载无水印图文视频
  • 暗黑破坏神2角色存档编辑器:Diablo Edit2完全指南
  • React Hooks原理:为什么不能写在if里?揭开Hook的“魔法”面纱
  • Jetson Nano GPIO编程避坑指南:从物理引脚、BCM到Tegra_SOC,三种模式到底怎么选?
  • 蓝莓成熟检测