ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot2+Vue3+MyBatis-Plus图书商城全栈项目实战详解

SpringBoot2+Vue3+MyBatis-Plus图书商城全栈项目实战详解 接手这个项目之前我对“图书电商网站”的认知还停留在Spring MVC JSP的时代。真正把SpringBoot2 Vue3 MyBatis-Plus MySQL8.0这套全栈组合落地跑通才发现里面的坑和细节远比想象中多。如果你正准备做一个Java Web方向的实战项目或者想把手里的老旧管理系统升级成前后端分离架构这篇文章就是为你准备的——我会从系统设计、核心技术选型、数据库建模到前后端实现把整个图书商城从零搭建的关键环节和踩坑实录都拆开讲清楚。这套项目源码的结构很典型前端是Vue3 Vite Element Plus后端是SpringBoot2 MyBatis-Plus数据库用MySQL8.0包含完整的用户登录注册、图书展示、购物车管理、订单处理、后台商品管理等模块还附带详细的开发文档。对于刚学完SpringBoot和Vue基础、想找一个完整项目练手的人来说它的技术栈足够主流代码规模又不会大到让人劝退。接下来的内容我会结合自己实际开发中的经验把每个环节的设计思路和实现细节一项项过一遍。1. 项目梳理与技术选型分析1.1 为什么是SpringBoot2 Vue3 MyBatis-Plus MySQL8.0这套组合先聊聊技术选型。这套组合在2024到2026年之间依然是Java Web项目里最常见的一套配置尤其是中小型系统和毕业设计、个人实战项目几乎快成了标准答案。很多人问为什么不用SpringBoot3原因很现实SpringBoot3强制要求JDK17而很多学校的教学环境、企业现有服务器还在用JDK8SpringBoot2.x JDK8的搭配依然拥有巨大的存量市场。更重要的是市面上大量的教程、开源代码、面试题都围绕SpringBoot2展开用这套技术栈踩坑时能搜到的资料最多学习成本最低。再来看前端。Vue3从2023年开始已经全面普及官方默认推荐使用组合式APIComposition API和script setup语法糖配合Vite构建工具开发体验比Vue2的Options API流畅不少。项目里用到的Pinia也是Vue3官方推荐的状态管理库比Vuex更轻量TypeScript支持也更友好。Element Plus作为Vue3对应的UI组件库后台管理界面里的表格、表单、弹窗、菜单基本都能直接覆盖不用自己去写复杂的样式逻辑。至于MyBatis-Plus它的核心价值在于把单表CRUD的样板代码几乎清零。以前用MyBatis写一个简单的用户查询要写Mapper接口、XML文件、ResultMap映射现在MyBatis-Plus直接用BaseMapper接口提供现成的selectById、selectList、insert等方法配合LambdaQueryWrapper做条件查询代码量至少减少一半。项目里图书列表的搜索、分页、条件筛选基本都是用它来完成的。MySQL8.0则是数据库层面的默认选项。8.0相比5.7有更好的性能和安全性默认字符集是utf8mb4对中文和Emoji的支持更完善窗口函数、CTE这些新特性也让复杂SQL的写法更简洁。唯一需要注意的是驱动和连接配置有些变化这个后面我会专门讲。1.2 系统功能模块全景图整个图书商城是一个典型的前后端分离电商系统按角色可以划分成两个端前台用户端和后台管理端。前台用户端面向普通用户核心流程是注册登录 → 浏览图书 → 搜索筛选 → 加入购物车 → 结算下单 → 查看个人订单。这里面还包含图书详情展示、分类浏览、关键字搜索这些辅助功能。前台页面的设计重点是体验流畅Vue3的响应式特性在图书列表的实时筛选、购物车数量联动这些场景下表现很直观。后台管理端面向管理员功能包括图书信息管理新增、编辑、上下架、删除、图书分类管理、订单处理查看订单详情、修改订单状态、用户管理、数据统计等。后台界面的重点是信息密度和操作效率Element Plus的el-table加el-form组合可以快速搭建出规范的管理界面。数据库层面核心表就那几张用户表user、图书表book、分类表category、购物车表cart、订单表orders、订单详情表order_item。这几张表之间的关系很清晰用户和订单是一对多订单和订单详情是一对多图书和分类是多对一。后文我给出具体的建表SQL和字段说明。1.3 项目目录结构与代码组织策略一个容易让人看得头疼的项目往往是目录结构混乱导致的。这套项目的目录组织按业界常见做法来划分后端采用标准的分层架构com.example.bookstore ├── controller // 接口层接收前端请求返回统一响应 ├── service // 业务层处理核心业务逻辑 │ └── impl // 业务实现类 ├── mapper // 数据访问层继承BaseMapper接口 ├── entity // 实体类对应数据库表结构 ├── common // 公共类统一返回结果、异常处理、常量 ├── config // 配置类跨域、拦截器、MyBatis-Plus配置 └── utils // 工具类JWT工具、MD5加密等这种分层的好处是单向依赖controller调serviceservice调mapper每一层各司其职。前端结构同样按功能模块划分src ├── api // 存放所有请求接口的封装 ├── assets // 静态资源 ├── components // 通用组件 ├── router // 路由配置 ├── store // Pinia状态管理 ├── views // 页面组件 │ ├── admin // 后台管理页面 │ └── shop // 前台商城页面 └── utils // 工具函数请求封装、Token处理等这样的目录组织一眼就能看懂后续想扩展功能模块时直接按照对应目录往里面加代码就行不需要在整个项目里到处找文件。2. 数据库设计核心细节与MySQL8.0部署2.1 MySQL8.0安装与Docker部署方案数据库环境是很多人卡壳的第一步。MySQL8.0的安装方式主要有两种本地直接安装和Docker容器化部署。本地安装适合日常学习和开发调试最省心的做法是下载MySQL Installer选择Server only一路下一步。需要特别注意的是8.0默认的认证插件是caching_sha2_password如果你的客户端工具比较老比如Navicat 15以前版本可能会报Authentication plugin caching_sha2_password cannot be loaded的错误。解决方法是修改用户的认证插件为mysql_native_password命令如下ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;Docker部署则是另一种更干净的方式特别适合想快速体验或者需要在多台机器间迁移环境的场景。安装Docker之后一条命令就能拉取并启动MySQL8.0docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e TZAsia/Shanghai \ -v /data/mysql:/var/lib/mysql \ mysql:8.0这条命令里几个参数值得解释一下-p 3306:3306把容器内的3306端口映射到宿主机的3306这样外部工具和SpringBoot才能连上-e MYSQL_ROOT_PASSWORD指定root密码-e TZAsia/Shanghai设置时区避免后面出现数据库时间与本地时间相差8小时的问题-v /data/mysql:/var/lib/mysql把容器内的数据目录挂载到宿主机这样容器删除后数据也不会丢。项目里实际使用时我个人推荐用Docker方式因为MySQL8.0在本机卸载再重装比较麻烦用Docker随时可以docker ps查看状态、docker logs mysql8查看日志排查问题效率高很多。2.2 核心数据表结构与字段设计数据库表设计是整个系统的地基表结构建得合理后面的业务逻辑就写得顺畅。这里给出几张关键表的建表SQL和设计思路。用户表userCREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 用户ID, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT 密码加密后, nickname varchar(50) DEFAULT NULL COMMENT 昵称, avatar varchar(255) DEFAULT NULL COMMENT 头像地址, email varchar(100) DEFAULT NULL COMMENT 邮箱, phone varchar(20) DEFAULT NULL COMMENT 手机号, role tinyint(4) NOT NULL DEFAULT 0 COMMENT 角色0-用户 1-管理员, status tinyint(4) NOT NULL 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), UNIQUE KEY uk_username (username) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT用户表;字段里面有几个值得关注的点role字段直接区分用户和管理员避免了单独建角色表、权限表的复杂度适合图书商城这种轻量级系统password字段存储的是加密后的密文不是明文这个安全习惯一定要养成status字段用于软禁用删除用户这种操作尽量别用物理删除否则订单表里关联的用户信息会变成脏数据。图书表bookCREATE TABLE book ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 图书ID, category_id bigint(20) NOT NULL COMMENT 分类ID, title varchar(200) NOT NULL COMMENT 书名, author varchar(100) DEFAULT NULL COMMENT 作者, publisher varchar(200) DEFAULT NULL COMMENT 出版社, isbn varchar(20) DEFAULT NULL COMMENT ISBN号, price decimal(10,2) NOT NULL COMMENT 价格, stock int(11) NOT NULL DEFAULT 0 COMMENT 库存, sales int(11) NOT NULL DEFAULT 0 COMMENT 销量, cover varchar(500) DEFAULT NULL COMMENT 封面图片地址, description text COMMENT 图书简介, status tinyint(4) NOT NULL 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_category_id (category_id), KEY idx_title (title) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT图书表;图书表是前台展示的核心数据源。price字段用decimal(10,2)而不是float或double是因为浮点数在计算金额时会产生精度误差这在任何涉及钱的系统里都是大忌。sales字段用来做销量排序和“热销榜”功能前台可以直接按这个字段倒序排列。status字段控制上下架下架的商品在前台检索时会被过滤掉。购物车表和订单相关表购物车表设计时有个关键决策购物车数据存数据库还是存前端本地我们选择存数据库原因很简单——用户换设备登录时购物车数据不会丢这是电商网站的基本要求。表结构包含user_id、book_id、quantity三个核心字段再加一个联合唯一索引(user_id, book_id)避免同一用户把同一本书加入购物车多次。订单表orders和订单详情表order_item是典型的主从表结构。订单表记录一次购买行为的总金额、下单时间、订单状态待付款、已付款、已发货、已完成、已取消、收货人信息订单详情表记录这次订单里包含的每一本书、购买数量、下单时的价格。为什么价格要冗余到订单详情里因为图书价格可能调整如果不冗余存储订单历史里看到的价格会跟着图书表一起变这在电商系统里是不能接受的。2.3 MyBatis-Plus与MySQL8.0的连接配置注意事项有了数据库表结构接下来就是让后端代码连上数据库。SpringBoot的application.yml里数据库连接配置这样写spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/bookstore?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 123456这里有三个关键点。第一驱动类名必须是com.mysql.cj.jdbc.Driver这是MySQL8.0的驱动类老式的com.mysql.jdbc.Driver在8.0里已经废弃写了会报警告甚至启动报错。第二URL里一定要加serverTimezoneAsia/Shanghai否则服务器和本地时区不一致会导致datetime字段读取时差8小时。第三allowPublicKeyRetrievaltrue这个参数很多人会漏掉如果不加用caching_sha2_password认证插件连接时可能会报Public Key Retrieval is not allowed错误。MyBatis-Plus的配置在application.yml里主要关注两点mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0map-underscore-to-camel-case: true让数据库的snake_case字段名如create_time自动映射到Java实体的camelCase属性createTime这能省去大量TableField注解。log-impl配置为StdOutImpl后控制台会打印执行的SQL语句和参数这在调试阶段排错非常有用上线前记得关掉。3. 后端关键模块实现从登录认证到订单处理3.1 SpringBoot2项目搭建与统一响应封装后端工程创建的基础步骤是在Spring Initializr里选择SpringBoot 2.7.x版本依赖选好Spring Web、MySQL Driver、Lombok生成工程后再手动引入MyBatis-Plus和JWT相关依赖。这一步我给个提醒SpringBoot2.7.x对应的MyBatis-Plus版本用mybatis-plus-boot-starter的3.5.x版本直接Maven引入即可不需要额外配置分页插件以外的内容。为了让前后端接口交互保持统一规范所有接口的返回值都用统一响应体包装。定义如下Data public class ResultT { private Integer code; // 状态码200成功其他失败 private String message; // 提示信息 private T data; // 返回数据 public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }统一响应体的好处是前端axios拦截器里只用判断code字段就能知道接口是否成功不用每个接口各写一套成功失败判断逻辑。前端请求封装里配合响应拦截器能实现全局错误提示和Token失效跳转登录页。3.2 JWT登录认证机制与请求拦截图书商城的认证方案采用JWTJSON Web Token它的核心思路是用户登录成功后服务端生成一个带签名和过期时间的Token返回给前端前端在后续请求的请求头里带上这个Token服务端通过拦截器验证Token的合法性从而识别用户身份。登录接口的核心逻辑是Service public class UserServiceImpl implements UserService { Autowired private UserMapper userMapper; Override public ResultUserVO login(String username, String password) { // 1. 根据用户名查询用户 LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.eq(User::getUsername, username); User user userMapper.selectOne(wrapper); // 2. 用户不存在或密码错误 if (user null || !MD5Utils.encrypt(password).equals(user.getPassword())) { return Result.error(用户名或密码错误); } // 3. 用户被禁用 if (user.getStatus() 0) { return Result.error(账号已被禁用); } // 4. 生成Token并返回用户信息 String token JwtUtils.generateToken(user.getId(), user.getUsername(), user.getRole()); UserVO userVO new UserVO(); BeanUtils.copyProperties(user, userVO); userVO.setToken(token); return Result.success(userVO); } }这里用LambdaQueryWrapper做条件查询的方式很常见MyBatis-Plus提供的这种链式写法既安全又能避免手写SQL。登录逻辑里需要先校验用户是否存在、密码是否正确、状态是否正常每一步失败都给出明确的错误提示——这是业务系统的基本素养。密码加密这里用的是MD5加盐处理。严格来说MD5的安全性已经不足以应对现代安全需求但在学习项目中仍然是教学常用的方式。如果你在做商业项目建议换成BCryptSpring Security自带BCryptPasswordEncoder安全性高很多。Token拦截器的实现方式是继承HandlerInterceptor在preHandle方法里从请求头拿到Authorization字段解析Token失败则直接返回401错误码。然后在WebMvcConfigurer里注册拦截器并配置放行路径——登录接口、注册接口、图书查询接口这些不需要认证的请求要放行购物车、订单等需要登录的接口则必须拦截。这种前后端分离的认证模式是主流方案理解它对你面试时讲项目帮助很大。3.3 购物车与订单模块的并发与事务处理购物车模块的核心操作是加入购物车。注意一个并发细节用户重复点击“加入购物车”按钮时如果库里还没有这条记录先去查一次没有再插入如果两条请求同时查到“不存在”就可能插入两条相同用户和相同图书的记录。解决方式就是前面设计表时提到的联合唯一索引(user_id, book_id)数据库层面兜底防止重复数据。代码里可以用saveOrUpdate方法结合唯一索引的冲突处理来保证最终结果正确。订单模块是整个系统里最需要小心的地方因为它涉及库存扣减和金额计算操作不当会出现超卖问题。创建订单的核心流程是前端提交订单携带购物车中选中的图书id列表和收货信息后端遍历图书逐一校验库存是否充足扣减库存更新销量计算订单总金额生成订单主表和订单详情表数据删除购物车中对应的记录这个过程必须加事务用Transactional注解保证要么全部成功要么全部回滚。扣减库存的SQL要写成原子操作UPDATE book SET stock stock - #{quantity} WHERE id #{bookId} AND stock #{quantity}这里的关键在于WHERE条件里带上了stock #{quantity}如果库存不足这条SQL影响行数为0代码里检查这个结果就可以判定库存不足并抛出异常从而避免超卖。这种写法比先查库存再扣减要安全得多因为查询和扣减之间的事务隔离问题会导致并发下超卖。3.4 MyBatis-Plus分页查询与条件筛选的实际应用后台管理系统的图书列表页最常见的需求是分页加多条件筛选。MyBatis-Plus的分页功能需要先配置分页插件Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }分页查询的写法public PageBook getBookPage(int pageNum, int pageSize, String keyword, Long categoryId, Integer status) { PageBook page new Page(pageNum, pageSize); LambdaQueryWrapperBook wrapper new LambdaQueryWrapper(); // 关键字模糊查询书名和作者 if (StringUtils.hasText(keyword)) { wrapper.and(w - w.like(Book::getTitle, keyword).or().like(Book::getAuthor, keyword)); } // 分类筛选 if (categoryId ! null) { wrapper.eq(Book::getCategoryId, categoryId); } // 状态筛选 if (status ! null) { wrapper.eq(Book::getStatus, status); } // 按创建时间倒序 wrapper.orderByDesc(Book::getCreateTime); return bookMapper.selectPage(page, wrapper); }这个查询里值得学的是LambdaQueryWrapper组合条件的写法。StringUtils.hasText()判断是否为空字符串避免前端传空字符串过来干扰查询wrapper.and()把书名like或作者like包成一组括号避免和后面的分类、状态条件产生逻辑混乱。4. 前端Vue3核心实现从环境搭建到页面开发4.1 Vue3项目搭建与工程化配置前端工程用的是Vite构建工具创建项目的命令是npm create vitelatest bookstore-web -- --template vue这个命令会生成一个Vue3 Vite的基础工程。接下来安装项目依赖npm install npm install vue-router4 pinia axios element-plus element-plus/icons-vue安装依赖阶段比较容易踩坑的是版本冲突。建议直接安装最新稳定版本Vue3项目如果出现运行时报错多半是版本不兼容导致的。Element Plus的完整引入方式是在main.js里import { createApp } from vue import ElementPlus from element-plus import element-plus/dist/index.css import zhCn from element-plus/es/locale/lang/zh-cn import App from ./App.vue const app createApp(App) app.use(ElementPlus, { locale: zhCn }) app.mount(#app)locale: zhCn这个配置很关键特别是日期选择器、分页组件的文字显示不配置中文包的话会显示英文。项目里还用到了路由懒加载在router/index.js里把页面组件改为动态导入{ path: /, name: BookList, component: () import(../views/shop/BookList.vue) }这样配置的好处是首屏只加载首页需要的代码不是把所有页面的代码一次性打包打开速度会明显快很多。4.2 组合式API与Pinia状态管理实战Vue3的script setup语法让代码组织清晰了不少。以前Vue2里数据、计算属性、方法散落在不同区块现在可以按业务维度聚在一起。拿购物车状态来举例用Pinia管理的核心代码// store/cart.js import { defineStore } from pinia export const useCartStore defineStore(cart, { state: () ({ cartItems: [], cartCount: 0 }), actions: { async fetchCartItems() { const res await api.getCartItems() this.cartItems res.data this.cartCount res.data.reduce((sum, item) sum item.quantity, 0) }, async addToCart(bookId, quantity) { await api.addToCart({ bookId, quantity }) await this.fetchCartItems() }, async removeFromCart(cartId) { await api.removeFromCart(cartId) await this.fetchCartItems() } } })为什么购物车要用全局状态管理而不是在组件里本地维护因为购物车的数据在头部导航栏、购物车页面、结算页面等多个组件里都要用到如果每个组件自己拉接口、自己维护数据就会出现数据不同步的问题。Pinia把数据集中在store里任何组件改动数据后其他组件通过响应式更新自动感知逻辑清晰且不易出错。cartCount这个计算属性在导航栏的商品图标上显示数量用户点击加入购物车后调用addToCart然后自动刷新cartItems和cartCount图标和页面数据实时联动这个交互体验是前端框架响应式能力的一个直观体现。4.3 axios请求封装与前端鉴权逻辑前端和后端交互统一走axios封装核心文件在utils/request.jsimport 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 token } return config }) // 响应拦截器统一处理返回值和错误 request.interceptors.response.use( response { const res response.data if (res.code 200) { return res } // Token过期跳转登录页 if (res.code 401) { localStorage.removeItem(token) localStorage.removeItem(userInfo) router.push(/login) } ElMessage.error(res.message) return Promise.reject(new Error(res.message)) }, error { ElMessage.error(网络请求异常请稍后重试) return Promise.reject(error) } ) export default request拦截器是前端工程里提升效率的核心设计。有了这层封装业务代码里调用接口时不需要关心Token怎么带上、报错怎么提示只需专注业务逻辑。比如登录接口调用时只写request.post(/user/login, data)拦截器会自动处理其他所有事情。开发环境下Vite还需要配置代理把前端的/api开头的请求转发到后端服务地址。在vite.config.js里配置export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这里需要说明的是changeOrigin: true的作用它会把请求头中的Host字段改为目标服务器地址防止后端在域名校验时拒绝请求。4.4 图书商城核心页面与交互设计前台商城的核心页面是图书列表页。这个页面的关键交互是左侧分类菜单加右侧图书卡片列表点击分类可以刷新图书列表顶部搜索框可以按关键字搜索。图书卡片显示封面、书名、作者、价格点击可以跳转详情页。价格显示有一个细节价格字段来自后端返回的price小数前端展示时需要格式化为两位小数可以直接用toFixed(2)或者封装成formatPrice函数统一处理。图书详情页包含图书简介、作者信息、库存状态和“加入购物车”按钮。库存不足时按钮要禁用并显示“已售罄”这个判断在页面渲染时做一次即可。加入购物车成功或者失败都用Element Plus的ElMessage提示给用户即时反馈比弹窗更轻量。后台管理的书籍列表页用el-table展示数据配合分页组件el-pagination每行操作列放“编辑”“删除”按钮。编辑弹窗用el-dialog加el-form实现表单提交前做简单的必填校验。后台管理的代码风格要跟前台区分开功能优先、界面简洁把重点放在操作效率上。5. 典型问题排查与实操避坑速查5.1 MySQL8.0连接与驱动问题汇总做这个项目时数据库相关的问题出现频率最高。下面整理一张对照表方便你遇到问题时直接查问题现象根本原因解决方案启动报错java.sql.SQLException: Unknown system variable query_cache_size驱动版本过低与MySQL8.0不兼容升级mysql-connector-java到8.x版本连接超时或握手失败未使用com.mysql.cj.jdbc.Driver驱动类修改driver-class-name配置报Connection is read-only事务上下文与连接配置冲突检查Transactional注解所在层确保数据源配置正确中文乱码数据库、连接URL、表字符集不一致统一使用utf8mb4字符集报Public Key Retrieval is not allowed认证插件为caching_sha2_password未开启公钥检索URL加allowPublicKeyRetrievaltrue5.2 MyBatis-Plus使用过程中的常见问题问题一分页查询返回总数为0或数据不完整这个问题的原因往往是分页插件没有注册。MyBatis-Plus的分页功能必须依赖PaginationInnerInterceptor缺少这个配置时selectPage会退化成普通的list查询。解决方式就是前面提到的在配置类中注册MybatisPlusInterceptor。问题二saveBatch批量插入性能差saveBatch内部默认不是真正的批量插入而是一条一条地insert性能不理想。如果数据量大可以配置批量SQL执行器或者在SQL注入器里自定义批量插入方法。对于图书商城这种低并发场景saveBatch够用不需要过度优化。问题三字段自动填充失效创建时间、更新时间这类字段更优雅的做法是用MyBatis-Plus的字段自动填充功能在实体类的字段上加TableField(fill FieldFill.INSERT)注解然后实现MetaObjectHandler接口统一填充。有些同学把逻辑写在业务代码里每个插入操作都要手动set时间反而是给自己添麻烦。5.3 Vue3开发过程中的高频报错与处理问题一组件渲染时xxx is not definedscript setup语法中模板里用的变量、函数必须从setup作用域中暴露。如果直接使用组件外部导入的工具函数而忘了引入就会出现这个报错。解决办法是检查模板中使用的每一个变量是否都在script setup中有定义。问题二el-form表单验证不生效Element Plus的el-form验证依赖rules对象和prop属性的对应关系。rules里的字段名必须和el-form-item的prop属性一致少了其中一个验证就不会触发。另外验证规则里的required为true时还需要设置message提示信息否则控制台会有警告。问题三页面刷新后路由跳转404这个问题通常出现在生产环境部署后。Vue3是单页应用路由由前端控制后端服务器没有配置try_files时访问/book/123这种路径会触发404。解决方式是在Nginx配置中加入location / { try_files $uri $uri/ /index.html; }开发环境下不用管生产部署一定要配置这条否则刷新页面就白屏。5.4 部署上线阶段的实践心得整个项目开发完成后的部署我习惯采用前后端分离部署方案后端打包成jar包运行前端打包成静态文件交给Nginx托管。后端打包命令mvn clean package -DskipTests生成的jar包在target目录下运行命令java -jar bookstore.jar --spring.profiles.activeprod我建议把生产环境和开发环境的配置分开用application-prod.yml存放生产环境数据库地址、日志级别等参数这样切换环境时不用反复改配置。前端打包命令npm run build生成的文件在dist目录把这个目录配置为Nginx的根目录同时把/api开头的请求反向代理到后端服务的8080端口server { listen 80; server_name yourdomain.com; root /usr/share/nginx/html; index index.html; location /api/ { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }这个部署方案的好处是前后端完全解耦后端服务升级时前端页面不受影响反之亦然。我实际部署时还遇到一个坑jar包的启动端口和Nginx代理端口冲突解决方式是把SpringBoot服务端口改成8081Nginx代理那边指向8081或者在配置里通过环境变量动态控制。最后再分享一个小技巧项目里的数据库脚本和接口文档一定要随手维护。你永远不知道三个月后回来改需求时是否还能记得当初为什么给order_item表加了一个remark字段。这套项目自带的文档虽然不算特别详尽但至少把表结构说明、接口地址和部署步骤记录下来了这本身就是很值的参考。如果你准备二次开发先把数据库脚本跑通、前后端启动起来再对照着文档把登录认证流程走一遍整个项目的脉络基本就能理清了。
返回列表