Vue 2.7 SpringBoot 3.1 MySQL 8 全栈开发超市商品管理系统实战超市商品管理系统是零售行业数字化转型的典型应用场景。对于计算机专业学生和全栈开发初学者而言通过一个完整的项目实践来掌握前后端技术栈的协同开发是提升工程能力的有效途径。本文将基于Vue 2.7、SpringBoot 3.1和MySQL 8技术组合从零开始构建一个功能完备的超市商品管理系统重点解决实际开发中的技术难点和常见陷阱。1. 开发环境准备与项目初始化1.1 开发工具配置工欲善其事必先利其器。在开始编码前我们需要确保开发环境配置正确Java开发环境JDK 17SpringBoot 3.1的最低要求Node.js环境建议安装LTS版本如16.xIDE选择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 dependenciesweb,mybatis-plus,mysql,lombok \ -d javaVersion17 \ -d artifactIdsupermarket-backend \ -d bootVersion3.1.0 \ -o supermarket-backend.zip前端项目初始化Vue 2.7# 使用Vue CLI创建项目 npm install -g vue/cli vue create supermarket-frontend # 选择Vue 2模板手动配置添加Router和Vuex1.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 数据库表结构设计超市管理系统的核心实体包括商品、商品类型、超市区域和货架。以下是优化后的DDLCREATE 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 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设计原则资源命名使用复数名词如/productsHTTP方法GET获取资源POST创建资源PUT全量更新PATCH部分更新DELETE删除资源状态码200成功201创建成功400客户端错误404资源不存在500服务器错误3.2 商品管理API实现商品管理是系统的核心模块我们采用三层架构Controller层处理HTTP请求RestController RequestMapping(/api/products) RequiredArgsConstructor public class ProductController { private final ProductService productService; GetMapping public ResponseEntityPageResultProductVO 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)); } }Service层业务逻辑处理Service RequiredArgsConstructor public class ProductServiceImpl implements ProductService { private final ProductMapper productMapper; Override Transactional(readOnly true) public PageResultProductVO listProducts(String name, Long categoryId, Integer page, Integer size) { PageProduct pageInfo new Page(page, size); LambdaQueryWrapperProduct queryWrapper new LambdaQueryWrapper(); queryWrapper.like(StringUtils.isNotBlank(name), Product::getName, name) .eq(categoryId ! null, Product::getCategoryId, categoryId); PageProduct productPage productMapper.selectPage(pageInfo, queryWrapper); return new PageResult(productPage.getTotal(), productPage.getRecords().stream().map(this::convertToVO).collect(Collectors.toList())); } }Mapper层数据持久化public interface ProductMapper extends BaseMapperProduct { 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 ResponseEntityErrorResponse handleBusinessException(BusinessException ex) { return ResponseEntity.status(ex.getStatus()) .body(new ErrorResponse(ex.getCode(), ex.getMessage())); } ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntityErrorResponse 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 service4.3 商品列表页面实现使用Element UI实现商品管理界面template div classproduct-container el-card shadownever div classfilter-container el-input v-modellistQuery.name placeholder商品名称 stylewidth: 200px keyup.enter.nativehandleFilter / el-select v-modellistQuery.categoryId placeholder商品分类 clearable stylewidth: 200px el-option v-foritem in categories :keyitem.id :labelitem.name :valueitem.id / /el-select el-button typeprimary clickhandleFilter查询/el-button /div el-table v-loadinglistLoading :datalist border fit highlight-current-row el-table-column labelID propid width80 / el-table-column label商品名称 propname / el-table-column label分类 propcategoryName / el-table-column label价格 propprice / el-table-column label库存 propstock / el-table-column label操作 width200 template #default{row} el-button sizemini clickhandleEdit(row)编辑/el-button el-button sizemini typedanger clickhandleDelete(row) 删除/el-button /template /el-table-column /el-table pagination v-showtotal0 :totaltotal :page.synclistQuery.page :limit.synclistQuery.limit paginationgetList / /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() }) }) } } } /script5. 系统部署与性能优化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?useSSLfalsecharacterEncodingutf8 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,metrics5.2 前端性能优化通过以下手段提升Vue应用性能路由懒加载const ProductList () import(./views/product/List.vue) const ProductEdit () import(./views/product/Edit.vue)代码分割在vue.config.js中配置module.exports { configureWebpack: { optimization: { splitChunks: { chunks: all, maxSize: 244 * 1024 // 244KB } } } }CDN引入常用库// vue.config.js module.exports { chainWebpack: config { config.externals({ vue: Vue, vue-router: VueRouter, axios: axios, element-ui: ELEMENT }) } }5.3 数据库性能调优针对超市管理系统的高频查询场景我们需要优化数据库索引优化-- 为高频查询字段添加索引 ALTER TABLE product ADD INDEX idx_category_status (category_id, status); ALTER TABLE product ADD INDEX idx_name (name);查询优化// 使用MyBatis Plus的selectPage优化分页查询 public PageProduct listProducts(int page, int size) { return productMapper.selectPage(new Page(page, size), Wrappers.ProductlambdaQuery() .select(Product.class, info - !info.getColumn().equals(description) !info.getColumn().equals(specification)) ); }缓存策略// 使用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); }