ARTICLE DETAIL

资讯详情

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

SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0网上租赁系统全栈开发实战

SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0网上租赁系统全栈开发实战 做Java Web项目SpringBoot2 Vue3 MyBatis-Plus MySQL8.0这套组合在目前的课程设计、毕业设计和中小型实战项目里基本是标配了。网上租赁系统这个题目我前后带过好几个学弟学妹落地自己也完整复现过几轮每次都能踩出一些新的坑。这篇就把整个项目的搭建思路、业务模型、核心代码实现、前后端联调过程以及那些常规文档里不会写的排查心得一次讲透。不管你是拿它做课设、毕设还是单纯想练手全栈这篇文章都能提供一套可以直接参考实践的完整方案。先说清楚这个东西能干什么它是一个基于B/S架构的在线租赁管理平台前端用Vue3做单页面应用后端用SpringBoot2提供RESTful接口MyBatis-Plus负责数据库操作MySQL8.0做数据持久化。系统核心覆盖了用户注册登录、租赁物品展示、在线下单租赁、订单管理、押金与租金计算、后台商品与用户管理等完整业务闭环。如果你正在找Java课程设计案例源码或者想搞明白SpringBoot2 Vue3这套前后端分离项目怎么从零组织起来这篇内容基本就是照着抄作业的级别。1. 项目定位与业务模型拆解1.1 网上租赁系统到底在做什么很多同学拿到这类项目标题第一反应是又是一个CRUD管理系统。这么理解没错但租赁系统比普通的管理系统多了一层业务逻辑这也是它适合做课设和毕设的原因既有常规的增删改查又有订单流转、费用计算、状态变更这些带业务味道的东西做出来的项目不会让人觉得太水。网上租赁系统的核心业务可以用一句话概括用户通过平台浏览可租赁的物品选择租赁周期后下单支付租金和押金使用完毕后归还物品并结算费用。围绕这个核心系统必须拆出几条业务线商品线租赁物品的分类管理、物品信息维护、库存状态可租/已租/下架、租金定价。订单线用户下单、订单状态流转待支付、租赁中、待归还、已完成、已取消、租金按天计算、押金冻结与退还。用户线普通用户注册登录、个人信息维护、历史订单查询管理员对用户和商家进行管理。运营线公告发布、租赁规则配置、后台数据统计这个可以作为加分项。如果只看技术不看业务这个项目就是前后端分离的标准脚手架但如果把业务逻辑设计清楚它就是一个能拿去答辩、能讲出设计亮点的完整系统。我在设计的时候建议把订单状态机作为核心亮点来讲这是最容易让老师觉得这个学生真的做了设计的地方。1.2 数据库设计租赁核心业务的落地数据库设计是整个项目的地基这一步做不好后面写代码全是补丁。网上租赁系统的数据库表不用追求多但每一张表都得有它存在的理由。我常用的表结构是这样的表名核心字段作用userid, username, password, nickname, phone, role, status用户表role区分管理员和普通用户categoryid, name, sort物品分类表productid, category_id, name, description, image, price_per_day, deposit, stock, status租赁物品表按天计费和押金ordersid, order_no, user_id, product_id, rent_days, daily_price, total_amount, deposit, status, create_time, return_time订单表记录每笔租赁交易announcementid, title, content, create_time公告表后台发布前台展示这里重点说两个容易出问题的设计细节第一订单表一定要设计order_no订单号字段不要直接用自增id给用户看。订单号可以用时间戳加随机数生成比如20250607153012 4位随机数。这既是业务习惯也是答辩时能拿出来讲的亮点。第二金额字段不要用float或double这是新手最常见的坑。租金、押金涉及计算和精度MySQL里应该用DECIMAL(10, 2)Java里对应BigDecimal。我在项目里看到过有人用double算租金结果出现0.30000000000000004这种值后期对账完全对不上只能返工。第三订单状态字段不要用枚举字符串散着存建议用tinyint存状态码在Java里用枚举类定义常量。这样数据库层面更省空间代码层面语义更清晰。2. 技术栈选型为什么是这四个组件2.1 SpringBoot2后端开发的免配置方案SpringBoot2在这个项目里的角色是后端基础框架。很多人写Spring项目最头疼的就是一堆XML配置SpringBoot的核心价值就是把这些配置约定大于配置给消灭掉内嵌Tomcat、自动配置、起步依赖让你把精力放在写业务代码而不是调环境。选SpringBoot2而不是SpringBoot3原因也很实在SpringBoot3强制要求JDK17而很多学校的课程设计环境还在JDK8SpringBoot2.7.x完美兼容JDK8生态里的第三方资料也最多出了问题一搜就能搜到答案。做课设和毕设稳定性和可查性比尝鲜更重要。我这边用的是SpringBoot 2.7.18这是2.x系列的最后一个版本bug修复最全。项目拿过来之后pom.xml里需要重点关注这几个核心依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.2/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /dependency注意MySQL8.0对应的驱动依赖是mysql-connector-j不是老项目的mysql-connector-java。如果你用SpringBoot2.7.x推荐用新驱动包省得老驱动在新版本下出现莫名其妙的兼容问题。2.2 MyBatis-PlusCRUD从手写到半自动MyBatis-Plus在这个项目里负责数据访问层。它本质上是一个MyBatis的增强工具核心价值在于单表CRUD不用写SQL全部由BaseMapper提供现成方法。这就是为什么它能大幅提升开发效率。举个例子用户表的selectByUsername如果纯用MyBatis写要写接口方法、写XML映射文件、写SQL语句三步。用MyBatis-Plus直接这样拿数据LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.eq(User::getUsername, username); User user userMapper.selectOne(wrapper);一行查询条件构造器就把问题解决了并且是类型安全的字段名写错了编译期就能发现不用等到运行时才报SQL异常。实际开发里80%以上的数据库操作都是单表查询和简单条件过滤MyBatis-Plus在这块的体验远超手写SQL。除了BaseMapperMyBatis-Plus还有一个杀手级功能是分页插件。租赁系统的商品列表和订单列表必须分页自己手写LIMIT语句还要处理总记录数统计比较麻烦。用MyBatis-Plus的PaginationInnerInterceptor配置一次分页就是Page对象传参的事插件自动拼接LIMIT和COUNT语句这又是个省心点。2.3 Vue3 Vite前端开发的新范式前端选Vue3 Vite在2025年已经没什么悬念了。Vue3的Composition API把逻辑聚合的能力提升了一大截Vite的开发服务器启动速度比Webpack快一个量级改代码热更新几乎是即时反馈开发体验完全是降维打击。在项目里Vue3的核心用法集中在几个点setup语法糖组件逻辑直接写在script setup里代码量比Vue2的Options API少很多。reactive和ref定义响应式数据computed做派生状态watch监听变化这套响应式核心要理解透。router-view配合Vue Router做页面切换动态路由做菜单权限控制。状态管理用Pinia比Vuex的API更简洁TypeScript支持也更好。UI组件库我推荐Element Plus因为它是目前Vue3生态里最成熟、文档最全、样式最贴近后台管理风格的组件库。像表格、表单、弹窗、分页这些后台系统高频组件Element Plus基本开箱即用不需要自己造轮子。2.4 MySQL8.0数据库选型没得商量MySQL8.0作为数据库主要优势是性能、稳定性和新特性。对于租赁系统这种量级的项目8.0的性能完全过剩但重要的是它已经成为当前的主流版本学习资料、问题解决方案、云数据库兼容性都是最新的。如果你是本地开发配置好my.ini的字符集为utf8mb4创建数据库时也要显式指定字符集CREATE DATABASE lease_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;字符集用utf8mb4是一个经验之谈。很多老项目的库表用utf8存中文没问题但存Emoji或者特殊符号就会报错因为utf8在MySQL里是utf8mb3最多3个字节而Emoji需要4个字节。前端用户昵称出现Emoji的情况很常见不提前注意这个坑后面上线了就会收到Data too long for column这类的报错。3. 环境准备与项目初始化3.1 版本搭配与本地环境开始动代码之前先把环境版本对齐。这个项目我测试过最稳的版本组合组件版本说明JDK1.88u202SpringBoot2.7的基石不要用JDK17Maven3.6.3依赖管理Node.js16.20.x 或 18.xVue3 Vite要求不要用太新的版本MySQL8.0.x数据库IDEIDEA 2023后端用IDEA前端也用IDEA或VSCode版本对齐是第一个隐藏坑。比如Node.js用20.x以上的高版本某些老一点的Vite插件可能不兼容启动直接报错。同理Maven版本太老拉不下来SpringBoot的依赖包也会卡死。我的建议是严格按照这个表来配减少了大量排查时间。本地安装MySQL8.0的时候安装方式推荐用官方安装包一路Next装完设置好root密码。安装完毕后打开终端验证mysql -u root -p出现Welcome to the MySQL monitor就说明装好了。如果你需要用Docker装MySQL8.0那就更省事直接跑一个容器搞定不需要在宿主机装一堆东西docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD{你的密码} \ -e MYSQL_DATABASElease_system \ -v mysql_data:/var/lib/mysql \ mysql:8.0Docker方式的好处是隔离干净不污染本地环境而且以后不想用了删容器就行数据还在卷里存着。3.2 从零创建SpringBoot后端工程后端工程我推荐用IDEA的Spring Initializr创建省时省力。创建时注意几个点Group填com.lease之类的自己项目标识。Artifact填lease-system。Java版本选8。依赖先选Spring Web和LombokMyBatis-Plus和MySQL驱动后加进pom。工程创建完后application.yml的配置是第一步。这里贡献一份实测可用的配置server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/lease_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: {你的密码} mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: db-config: id-type: auto这个配置里有三个细节必须说明白第一serverTimezoneAsia/Shanghai必须加。MySQL8.0的时区设置默认不是中国时区不加这个参数插入时间字段时会报The server time zone value的异常。第二allowPublicKeyRetrievaltrue必须加。MySQL8.0的认证插件默认用caching_sha2_password有些驱动版本连接时会报公钥检索错误加上这个参数能绕过。第三map-underscore-to-camel-case: true必须开。数据库字段是create_timeJava属性是createTimeMyBatis-Plus默认是支持这种下划线转驼峰的但确保配置文件里这个开关开着避免出现字段映射不上的灵异事件。3.3 创建Vue3前端工程前端工程创建用Vite官方脚手架最方便npm create vitelatest lease-web -- --template vue cd lease-web npm install装完基础依赖之后再把项目实际要用的库装上npm install vue-router4 pinia axios element-plus element-plus/icons-vue装完之后建议第一时间配置vite.config.js的代理把开发环境的API请求转发到后端8080端口这样联调阶段不会踩跨域坑import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })代理配好后前端所有/api开头的请求都会被Vite转发到后端。这里有个小坑值得提前说后端接口路径建议统一定义为/api/xxx格式前端代理只针对这个前缀生效前后端路径约定一致联调阶段能省下很多事。4. 后端核心模块实现细节4.1 实体类与数据访问层后端代码的组织方式我习惯按entity、mapper、service、controller四层分。第一次接触这种分层的同学可以这样理解实体类是数据库表的映射Mapper负责直接跟数据库打交道Service处理业务逻辑Controller只接收请求和返回结果。一层管一件事出问题的时候定位很快。以用户实体类为例配合Lombok代码量很精简Data TableName(user) public class User { TableId(type IdType.AUTO) private Long id; private String username; private String password; private String nickname; private String phone; private Integer role; // 0-普通用户 1-管理员 private Integer status; // 0-禁用 1-正常 private LocalDateTime createTime; }订单实体类要稍微复杂一点核心业务字段都在这里Data TableName(orders) public class Order { TableId(type IdType.AUTO) private Long id; private String orderNo; private Long userId; private Long productId; private Integer rentDays; private BigDecimal dailyPrice; private BigDecimal totalAmount; private BigDecimal deposit; private Integer status; // 0-待支付 1-租赁中 2-待归还 3-已完成 4-已取消 private LocalDateTime createTime; private LocalDateTime returnTime; }Mapper层的代码比想象中还简单因为MyBatis-Plus已经把CRUD都封装好了Mapper public interface UserMapper extends BaseMapperUser { }就这么一行单表的增删改查、条件查询就全都有了。如果后续要连表查询就在Mapper里加方法配XML文件写SQL但作为租赁系统的基础功能BaseMapper提供的接口已经覆盖90%的场景了。4.2 Service业务层与Controller接口层Service层是业务逻辑的重点租赁系统的核心计算全在这一层。以创建订单为例这块代码能讲清楚整个业务闭环Service public class OrderServiceImpl extends ServiceImplOrderMapper, Order implements OrderService { Resource private ProductMapper productMapper; Override Transactional(rollbackFor Exception.class) public Order createOrder(OrderCreateDTO dto) { // 1. 查询商品信息 Product product productMapper.selectById(dto.getProductId()); if (product null) { throw new RuntimeException(商品不存在); } if (product.getStatus() ! 1 || product.getStock() 0) { throw new RuntimeException(商品暂不可租赁); } // 2. 计算订单金额 BigDecimal totalAmount product.getPricePerDay() .multiply(BigDecimal.valueOf(dto.getRentDays())); // 3. 生成订单号并保存 Order order new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(dto.getUserId()); order.setProductId(product.getId()); order.setRentDays(dto.getRentDays()); order.setDailyPrice(product.getPricePerDay()); order.setTotalAmount(totalAmount); order.setDeposit(product.getDeposit()); order.setStatus(0); this.save(order); // 4. 扣减库存 product.setStock(product.getStock() - 1); if (product.getStock() 0) { product.setStatus(0); // 库存为0自动下架 } productMapper.updateById(product); return order; } }这段代码有几个值得在答辩时讲的细节Transactional加在创建订单这种多表操作上。创建订单同时要操作订单表和商品表扣库存任何一步失败都应该回滚不加事务就会出现订单创建了但库存没扣的数据不一致问题。金额计算用BigDecimal.multiply这是前面提到的精度问题的落实。天数和单价相乘一天的租金是两位小数乘以天数后可能有4位小数最终要setScale(2, RoundingMode.HALF_UP)保留两位。库存扣减在创建订单时完成这是租赁系统的核心设计。普通电商是下单不扣库存支付才扣租赁系统因为涉及租期占用下单即锁定资源更合理。Controller层的代码就比较机械了接收参数、调Service、返回统一结果RestController RequestMapping(/api/order) public class OrderController { Resource private OrderService orderService; PostMapping(/create) public ResultOrder createOrder(RequestBody OrderCreateDTO dto) { Order order orderService.createOrder(dto); return Result.success(order); } GetMapping(/myOrders) public ResultPageOrder myOrders(RequestParam Long userId, RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize) { PageOrder page orderService.page( new Page(pageNum, pageSize), new LambdaQueryWrapperOrder() .eq(Order::getUserId, userId) .orderByDesc(Order::getCreateTime)); return Result.success(page); } }这里返回的ResultT是统一响应体包含code、message、data三个字段。全项目统一返回这个对象前端就能统一处理错误和成功而不是每个接口返回不同结构前端每次都要单独解析。4.3 登录认证与拦截器租赁系统虽然是课设级项目但登录认证逻辑必须有。我用的方案是JWT 拦截器实现简单、无状态、跨域友好。后端生成Token的核心代码String token Jwts.builder() .setSubject(user.getUsername()) .claim(userId, user.getId()) .claim(role, user.getRole()) .setExpiration(new Date(System.currentTimeMillis() 1000 * 60 * 60 * 24)) .signWith(SignatureAlgorithm.HS256, mySecretKey) .compact();有了Token之后写一个拦截器统一做鉴权Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行OPTIONS预检请求 if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (StringUtils.hasText(token)) { try { Claims claims Jwts.parser() .setSigningKey(mySecretKey) .parseClaimsJws(token) .getBody(); request.setAttribute(userId, claims.get(userId)); return true; } catch (Exception e) { response.setStatus(401); return false; } } response.setStatus(401); return false; } }这个拦截器的几个要点第一OPTIONS请求必须放行。浏览器跨域请求在发正式请求之前会先发一个OPTIONS预检如果拦截器把预检请求拦截了前端就会报跨域错误。第二拦截器注册时要注意路径匹配。用户登录、商品列表这些接口不需要鉴权管理员接口才需要。我建议把注册配置写成这样拦截/api/order/**、/api/admin/**等需要登录的路径放行/api/user/login、/api/user/register、/api/product/list这些公开接口。4.4 跨域与参数校验前后端分离项目跨域问题绕不开。虽然Vite代理能解决开发环境的跨域但部署后前后端在不同域名时还是要后端配合。最好的方案是全局配置跨域Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }参数校验方面SpringBoot的Validated注解值得用起来。比如OrderCreateDTO里Data public class OrderCreateDTO { NotNull(message 商品ID不能为空) private Long productId; NotNull(message 用户ID不能为空) private Long userId; Min(value 1, message 租赁天数至少为1天) Max(value 365, message 租赁天数不能超过365天) private Integer rentDays; }Controller方法参数上加Validated前端传了非法参数后端会直接返回带错误信息的400响应不需要自己在代码里写一堆if判断代码整洁度提升明显。5. 前端核心功能实现要点5.1 路由、状态管理与Axios封装Vue3项目的起点是入口和路由。main.js里把所有模块都注册好import { createApp } from vue import App from ./App.vue import router from ./router import { createPinia } from pinia import ElementPlus from element-plus import element-plus/dist/index.css const app createApp(App) app.use(router) app.use(createPinia()) app.use(ElementPlus) app.mount(#app)路由文件里定义页面路径和组件映射。租赁系统的路由结构建议分成两块前台用户页面和后台管理页面通过路由meta字段标记是否需要管理员权限const routes [ { path: /, component: () import(/layout/FrontLayout.vue), children: [ { path: , component: () import(/views/Home.vue) }, { path: product/:id, component: () import(/views/ProductDetail.vue) }, { path: cart, component: () import(/views/Cart.vue) }, { path: login, component: () import(/views/Login.vue) }, { path: register, component: () import(/views/Register.vue) }, { path: orders, component: () import(/views/MyOrders.vue) } ] }, { path: /admin, component: () import(/layout/AdminLayout.vue), meta: { role: 1 }, children: [ { path: , redirect: /admin/product }, { path: product, component: () import(/views/admin/ProductManage.vue) }, { path: order, component: () import(/views/admin/OrderManage.vue) }, { path: user, component: () import(/views/admin/UserManage.vue) } ] } ]Axios封装这块值得多花点功夫。统一封装后能避免每个页面重复处理Token、错误提示、返回解构的问题import axios from axios import { ElMessage } from element-plus import { useUserStore } from /stores/user import router from /router const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器自动携带Token request.interceptors.request.use(config { const userStore useUserStore() if (userStore.token) { config.headers.Authorization userStore.token } return config }) // 响应拦截器统一处理错误码 request.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res }, error { if (error.response?.status 401) { ElMessage.error(登录状态已过期请重新登录) router.push(/login) } else { ElMessage.error(error.message || 网络异常) } return Promise.reject(error) } ) export default request这个封装里请求拦截器负责从Pinia里拿Token塞到请求头响应拦截器负责统一处理401登录过期、业务错误码、网络异常。页面里调接口只需要关心数据本身不用重复处理这些样板逻辑。5.2 核心页面实现商品列表与下单流程商品列表页是Vue3核心能力的集中体现。用reactive管理查询条件绑定Element Plus的表格和分页组件通过onMounted生命周期加载数据一个大列表页就成型了template div classproduct-list el-input v-modelqueryForm.keyword placeholder搜索商品名称 clearable inputloadProducts / el-select v-modelqueryForm.categoryId placeholder选择分类 clearable changeloadProducts el-option v-forcat in categories :keycat.id :labelcat.name :valuecat.id / /el-select el-table :dataproductList stripe el-table-column propname label商品名称 / el-table-column proppricePerDay label日租金 / el-table-column propdeposit label押金 / el-table-column propstock label库存 / el-table-column label操作 template #default{ row } el-button typeprimary sizesmall clickgoDetail(row.id)查看详情/el-button /template /el-table-column /el-table el-pagination v-model:current-pagequeryForm.pageNum v-model:page-sizequeryForm.pageSize :totaltotal layouttotal, prev, pager, next current-changeloadProducts / /div /template script setup import { reactive, ref, onMounted } from vue import { useRouter } from vue-router import request from /utils/request const router useRouter() const productList ref([]) const categories ref([]) const total ref(0) const queryForm reactive({ keyword: , categoryId: null, pageNum: 1, pageSize: 10 }) const loadProducts async () { const res await request.get(/product/page, { params: queryForm }) productList.value res.data.records total.value res.data.total } const loadCategories async () { const res await request.get(/category/list) categories.value res.data } const goDetail (id) { router.push(/product/${id}) } onMounted(() { loadProducts() loadCategories() }) /script写Vue3有一点特别值得注意reactive适合嵌套对象ref适合基本类型和简单变量。列表数据我习惯用ref包裹因为它是基本变更用ref语义更清晰。查询条件这种固定的对象结构用reactive更顺手。下单流程页面是另一个前端核心。用户选择租赁天数后前端要实时计算应付金额这里用computed最合适const rentDays ref(1) const product ref(null) const totalAmount computed(() { if (!product.value) return 0 return product.value.pricePerDay * rentDays.value }) const handleOrder async () { const res await request.post(/order/create, { productId: product.value.id, userId: userStore.userId, rentDays: rentDays.value }) ElMessage.success(下单成功) router.push(/orders) }computed的好处是它会根据rentDays和product自动重新计算页面上的金额显示会自动更新不用手动触发任何更新逻辑。这种声明式的写法比命令式去修改一个total值变量简洁太多了。5.3 联调阶段的前端代理配置前后端联调是很多人第一次项目卡壳的地方。明明后端接口单独测试能用Postman调通前端页面一请求就报错十有八九是代理或跨域配置问题。前面已经配置了/api代理但开发阶段还有一个细节后端接口要统一加/api前缀。比如商品接口路径是/api/product/page那Controller类上的RequestMapping就直接写RequestMapping(/api/product)。这样前端请求/api/product/pageVite代理转发到http://localhost:8080/api/product/page后端能正确匹配。如果代理配置了但还是连不上优先排查这几个点后端启动了吗最基础但最容易忽略IDEA里看控制台有没有SpringBoot启动成功的Banner。后端接口能被直接访问吗浏览器地址栏直接输http://localhost:8080/api/product/page能返回JSON就说明后端没问题。代理配置生效了吗浏览器F12打开Network面板看请求的URL是不是/api开头Response是不是正常JSON。联调通了之后还要注意登录态的保存。Token存在Pinia里刷新页面就没了需要配合localStorage持久化// stores/user.js export const useUserStore defineStore(user, { state: () ({ token: localStorage.getItem(token) || , userInfo: JSON.parse(localStorage.getItem(userInfo) || null) }), actions: { setLogin(token, userInfo) { this.token token this.userInfo userInfo localStorage.setItem(token, token) localStorage.setItem(userInfo, JSON.stringify(userInfo)) }, logout() { this.token this.userInfo null localStorage.removeItem(token) localStorage.removeItem(userInfo) } } })这里把localStorage作为Token和用户信息的持久化存储刷新页面后从localStorage恢复状态登录态就不会丢了。6. 常见问题与排查技巧实录6.1 环境与依赖类问题依赖冲突是Maven项目最常见的坑。如果启动后端时报类似ClassNotFoundException或者NoSuchMethodError基本可以确定是依赖版本冲突优先用Maven Helper插件排查打开pom.xml在依赖关系图里看有没有红色冲突线。冲突的依赖用exclusion标签排除掉保留自己想要的那个版本。Node版本问题也很常见。Vite 4要求Node 14.18或16Vite 5要求Node 18。如果你用的是老教程的项目package.json里的vite版本是4.x但本地Node是20或22启动时会报You are using Node.js 20.x.x but Vite requires Node.js ^14.18.0 || 16.0.0解决方式很简单用Node版本管理工具切到16或18版本。前端依赖装失败是另一个高频问题建议换淘宝镜像源npm config set registry https://registry.npmmirror.com装完后npm run dev能正常启动说明依赖没问题。6.2 数据库连接与驱动问题数据库问题是这个项目里报错最集中的区域。整理一张排查速查表报错信息原因解决方案The server time zone value 乱码时区未配置JDBC URL加serverTimezoneAsia/ShanghaiPublic Key Retrieval is not allowedMySQL8.0认证插件问题JDBC URL加allowPublicKeyRetrievaltrueAccess denied for user rootlocalhost密码错误或用户权限检查数据库密码和账号权限Unknown database lease_system数据库没创建执行CREATE DATABASE lease_systemTable xxx doesnt exist表没建或没执行SQL初始化脚本导入项目附带的SQL文件尤其要注意项目标题里写着含文档一般的源码包都会附带lease_system.sql初始化脚本。拿到项目第一步一定先用Navicat或命令行客户端执行这个SQL文件把库、表、初始数据都建好再启动后端。很多人上来直接跑后端结果报Table doesnt exist就是因为漏了这步。6.3 前后端联调问题联调阶段最典型的报错是401 Unauthorized。原因通常是请求没带Token或Token过期。排查路径F12打开Network面板看请求头的Authorization字段有没有值。没有值说明userStore.token是空的要么没登录要么登录逻辑没把Token存进Pinia。有值但还是401把Token放到 jwt.io 上解析一下看是不是过期了过期时间是不是设置错了。另一个典型问题是CORS error。虽然开发环境配置了Vite代理但如果你用了Postman测试、直接访问了后端地址或者部署到了生产环境还会遇到跨域。后端全局跨域配置必须提前配上这是双保险。6.4 功能逻辑问题有些问题不报错但功能就是不对。这类静默bug最让人头疼。比如商品列表查不出来数据但接口返回是200。这种情况先查数据库里有没有数据再查查询条件是不是传了不存在的categoryId。还有可能是商品状态字段默认为0下架列表接口只查询status1上架的商品初始数据里商品状态如果没设为1前端自然看不到。再比如用户注册成功后无法登录。排查password字段是不是在数据库里明文存储而登录逻辑做了MD5加密。只要初始数据的密码和注册逻辑的加密方式不一致就会出现这种明明注册成功却登录不上的问题。解决方式是把初始SQL里的密码字段手动改成注册时相同加密算法生成的值。还有一个订单状态流转的问题。订单创建后状态一直是0待支付功能看起来正常但用户看不到后续的租赁中、已归还等状态。这是因为初始SQL里只插入了订单基础数据没有做状态流转的触发逻辑。如果项目说明文档里没写支付模拟流程那就在代码里给个模拟支付按钮把订单状态从0改成1这样整个订单生命周期就能跑通了。项目后续还能怎么扩展最后分享一个个人建议。如果你打算把这个项目作为毕设或者面试作品来准备一定不要止步于基础的CRUD。我在实际使用中发现有几个方向的扩展性价比很高第一加一个数据可视化大屏。后台首页放几个ECharts图表每日订单量趋势、租赁物品热度排行、分类占比饼图、用户增长折线图。技术上就是后端写几个统计SQL前端引入ECharts画图但展示效果提升非常大答辩的时候特别加分。第二给订单流程加一个退款/结算功能。目前系统里订单状态到已完成就结束了如果加一个归还结算的逻辑比如早归还按实际天数退款、逾期按倍数罚款业务的完整度立刻就上来了。第三把文件上传功能补上。物品图片目前大概率是存URL地址如果想演示完整的上传流程加一个本地文件上传接口把图片保存到服务器指定目录再返回一个可访问的URL给前端展示。这也是面试官常问的一个点。每次有人问我要这个项目的源码和学习路径我都会提醒一句源码拿到手不是终点把项目的每个表、每个接口、每个状态的流转逻辑彻底弄明白能自己动手改出几个功能这才算是真正把SpringBoot2 Vue3 MyBatis-Plus这套技术栈学到手了。毕竟面试的时候面试官问的不是你有没有跑起来一个项目而是这个项目里你负责的部分是怎么设计、怎么实现的。
返回列表