ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue+MyBatis管理系统开发实战:从架构设计到部署

SpringBoot+Vue+MyBatis管理系统开发实战:从架构设计到部署 做过不少毕设和中小型管理系统但把整个项目从架构到部署串起来讲清楚的并不多。今天借着这套“乐享田园系统”完整拆一遍基于SpringBootVueMyBatisMySQL的管理系统到底该怎么设计、怎么写、怎么避坑。项目本身是田园生活场景下的综合管理系统核心业务围绕田园地块、认养订单、农户管理、内容公告等模块展开。我尽量按真实开发流程来讲从数据库设计一路到前端打包中间会把我在实际项目里踩过的坑和解决办法都写出来希望对正在做同类管理系统、毕业设计或者刚转后台开发的朋友有点帮助。1. 项目定位与业务逻辑先想清楚系统要给谁用1.1 田园系统到底解决什么样的问题很多人拿到类似的项目第一反应就是“做增删改查”但真正动手前业务边界不清晰后面所有代码都会跟着乱。乐享田园系统的场景可以这样理解城市周边有农业用地和田园体验项目经营者需要一套系统来管理田园地块信息、让用户在线认养或租赁地块、跟踪订单状态、发布田园活动公告同时后台要有管理员对入驻农庄、基地信息进行审核维护。也就是说这套系统不是单纯的“商城”也不是纯粹的“内容管理系统”它把地块资源、订单交易、内容发布、用户管理整合到了一起。核心用户角色至少分三类普通用户、地块经营者或农户、系统管理员。普通用户看田园资源、下单认养、查看个人订单经营者管理自己名下的地块信息、上下架产品和处理订单管理员维护整个平台的基地数据、分类标签、公告内容和用户状态。业务流理顺后系统模块就自然出来了登录注册、首页展示、田园地块列表、认养/租赁下单、订单管理、收藏与评论、公告资讯、后台数据统计。这些模块不是并列的它们之间有明确的依赖关系。比如订单模块依赖地块模块地块模块依赖分类和基地数据用户模块是所有模块的基础。这个从“角色”反推“模块”的过程我建议每个人做之前都先在文档里写一遍哪怕只是简单的几行字。因为后面建表、写接口、做权限控制全都依赖这套角色和模块划分。如果一开始没想清楚最容易出现的后果是数据库字段建了一堆接口也写了一堆但页面调用时发现数据对不上逻辑经常要回炉。1.2 前后端分离模式下的访问流程这套系统采用前后端分离开发方式但最终部署可以合在一起这也是国内中小型管理系统最主流的形态。前端用Vue负责页面交互后端用SpringBoot提供接口数据统一走MySQL。一次完整请求的实际流程是这样的用户在页面输入账号密码Vue把表单数据通过Axios发送到后端登录接口后端Controller接收请求调用Service里的用户校验逻辑再通过Mapper访问MySQL校验成功后会生成Token返回给前端前端把Token存到本地后续每次请求都在请求头带上Token后端通过拦截器或过滤器校验Token是否有效有效才放行访问受保护接口。这套流程看起来简单但涉及的问题并不少比如Token过期怎么处理、权限不足怎么返回统一格式、跨域怎么解决的都是实打实的开发细节。之所以选择把前后端分开而不是像传统JSP那样混在一起核心原因是职责清晰。前端专注页面渲染和用户交互后端专注业务逻辑和数据部署时前端构建出的静态文件可以直接复制到SpringBoot的static目录下这样对外就只有一个服务端口省掉了单独部署Nginx的复杂度也给想做前后端分离但不想搞太多运维工作的同学提供了一条简单路径。2. 技术选型拆解与版本匹配2.1 SpringBoot、Vue、MyBatis、MySQL各自承担什么技术栈的每个组件都有自己的位置理解各自的边界是合理选型的前提。SpringBoot负责提供HTTP接口、依赖注入、事务管理、配置管理等后端骨架能力。选它不是因为“现在都这么用”而是因为它让Java后端开发变得非常轻量内嵌Tomcat无需额外部署Servlet容器自动配置极大减少了XML配置配合Maven或Gradle管理依赖整个项目结构清晰、启动快。Vue负责前端的所有动态交互。对于管理系统这种大量表单、列表、弹窗、路由跳转的界面来说Vue的响应式数据绑定能大幅减少DOM操作的代码量。配合Element UI这类组件库一个后台管理界面可以快速拼装出来而且风格统一切片方便。MyBatis负责数据库访问层。相比Spring Data JPAMyBatis的SQL是显式写在Mapper里的对于中大型管理系统特别友好。因为管理系统有不少联表查询、条件动态筛选、分页统计这些SQL用MyBatis可以精确控制不会出现JPA那种复杂查询时自动生成低效SQL的问题。MySQL则是存储和持久化的基石所有用户数据、订单数据、地块数据都保存在这里。选用MySQL的理由非常务实免费、稳定、资料多、社区活跃遇到问题基本都能搜到解决方案。对于中小型系统它的性能和可靠性完全够用。2.2 版本匹配是个坑别上来就装最新的热点搜索里有个词叫“SpringBoot版本太高”这真的不是空穴来风。我用Spring Boot 3.x遇到过不少麻烦最典型的是javax.servlet换成了jakarta.servlet很多老教程里的javax依赖直接编译不通过。另外3.x对JDK版本有硬性要求JDK8无法使用必须上JDK17及以上。如果你是本地环境是JDK8最稳的组合是JDK8 SpringBoot 2.7.x MyBatis-Spring-Boot-Starter 2.3.x MySQL Connector 8.0.x。这个组合经过大量项目验证网上资料也最多排查问题的时候基本不会被困在版本兼容性上。前端方面Vue 2和Vue 3的选择也直接影响后续开发。建议新项目直接使用Vue 3 Vite Element Plus。Vue 3的Composition API在处理复杂页面逻辑时明显比Vue 2的Options API顺手而且Element Plus组件库本身就为Vue 3设计。Vite的开发服务器启动速度比webpack快很多对于频繁调试的本地开发体验提升非常明显。常用的版本组合我整理了一个参考组件推荐版本说明JDK1.8或11不要用17除非你有把握处理兼容性SpringBoot2.7.182.x最终版本稳定、资料多MyBatismybatis-spring-boot-starter 2.3.x与SpringBoot 2.x配套MySQL8.0.x注意时区配置Node.js18.x或20.xVite构建需要Vue3.4.x Vite新项目建议直接上Vue32.3 为什么不用更复杂的微服务架构有的同学会有困惑为什么这种系统不直接用微服务或者引入Redis、MQ等技术栈原因很简单——项目体量决定架构形态。乐享田园系统属于典型的中小型管理系统用户量级和并发量级都不大单体应用加上合理优化已经能非常平稳地运行。盲目引入微服务会增加部署复杂度、硬件成本、运维成本反而让项目变得难以维护。但单体不代表粗糙代码里仍然要严格分层Controller层只做参数接收和响应封装Service层承载业务逻辑Mapper层只做数据库访问。这种分层一方面让代码可读性强另一方面也为将来升级留存了可能性。假如某天地块管理模块需要抽成独立服务直接挪动Service实现类并新增对应的网络接口就可以改动成本远低于把所有逻辑混在一起的写法。3. 数据库表结构设计思路与实操展示3.1 核心表的设计逻辑由业务流推导出来的结构数据库设计是整个系统里我最看重的一部分它决定了后续所有开发环节是否顺畅。乐享田园系统的核心表可以按业务流程来推导。从“用户”出发需要user表存储账号密码、昵称、手机号、角色等基本信息从“田园地块资源”出发需要base表存基地名称、位置、介绍、封面图需要category表存地块类型比如“菜园认养”“果园认养”“田园民宿”需要farmland表存具体地块信息包括面积、价格、状态、所属基地、所属经营者。从“交易”出发需要farmland_order表存订单信息包括用户ID、地块ID、开始时间、租期、金额、订单状态从“互动内容”出发需要comment表存用户对地块的评价需要announcement表存系统公告或田园资讯。为了提升后台统计效率还可以增加view_log或简单的访问统计表。这里最核心的关联关系是一个基地base下可以有多个地块farmland一个地块可以被多条订单farmland_order引用一个用户可以拥有多条订单。外键关系可以通过逻辑关联实现不一定非要数据库物理外键。我实际开发时一般不加物理外键靠Service层保证数据一致性。这样做的好处是插入数据和清理数据更灵活坏处是需要开发者心里有数不能把脏数据写进去。3.2 主要表的字段设计与场景说明这里举例说明地块表farmland的完整字段设计字段名类型说明idbigint主键自增namevarchar(64)地块名称base_idbigint所属基地IDcategory_idbigint地块分类IDareadecimal(10,2)地块面积平方米pricedecimal(10,2)认养价格unitvarchar(16)计价单位月/年/一次性descriptiontext地块详细介绍cover_imagevarchar(255)封面图片URLstatustinyint0下架 1上架 2已认养create_timedatetime创建时间update_timedatetime更新时间设计字段时有几个经验值得强调。第一价格和时间尽量用decimal和datetime不要用float或字符串否则算钱的精度和时间的查询排序都会出问题。第二状态字段用tinyint加注释说明含义比用字符串更节省空间且便于索引。第三每张表都保留create_time和update_time这两个审计字段排查数据问题的时候会省很多功夫。farmland_order订单表涉及的核心字段则偏向业务推进order_no生成一个唯一订单号status表示待支付/已支付/已取消/已完成amount记录实际支付金额start_date和end_date计算租期。我建议把订单号和用户ID都建上索引这是两张查询频率极高的表索引直接决定列表查询能不能撑住。3.3 索引、字段类型与建表细节的实操经验在实际建表过程中有几个容易被忽略的细节值得专门说。字符集统一设置utf8mb4不要只设utf8。因为utf8在MySQL中最多只能存3字节的字符像一些有趣的生僻字或特殊表情符号会报错或乱码。utf8mb4是完整的UTF-8编码才能保证内容不乱。时间字段在MySQL 8.0中建议datetime(0)即可不要给自己找麻烦用timestamp带时区问题。连接字符串上一定要加serverTimezoneAsia/Shanghai否则容易出现8小时时差问题。关于索引一个很实用的原则是查询最频繁的字段建立索引但不要每列都加索引。比如farmland表里status字段如果系统经常按上下架状态筛选可以加索引如果数据量不大加索引的效果也不明显。索引是加速查询的杠杆也是写操作的负担权衡好才合理。4. 后端核心功能实现从登录鉴权到订单闭环4.1 登录注册与Token鉴权怎么落地后端开发的第一步往往是用户模块但很多项目里的用户模块写得很潦草。乐享田园系统推荐采用JWT做无状态鉴权理由是不需要像Session那样占用服务端内存适合前后端分离场景。实现步骤一般是这样用户登录时Controller接收用户名和密码Service中先校验参数非空然后通过userMapper.findByUsername查询用户再用MD5或BCrypt对密码进行验证。密码正确后生成Token通过io.jsonwebtoken的Jwts.builder()设置用户ID、角色、过期时间以及签名密钥最后把Token和用户基本信息返回给前端。需要注意两个细节。第一密码存储不能是明文至少要MD5加盐。如果追求更安全可以在Spring Security里启用BCrypt加密。第二Token的密钥要放入application.yml配置文件中不要硬编码到代码里后期更换密钥只需要改配置重新启动即可。Token校验用拦截器做。SpringBoot里实现HandlerInterceptor接口在preHandle方法里从请求头Authorization中取出Token解析失败或过期就直接返回401同时把错误信息包装成统一JSON格式。对于登录、注册、首页枚举列表这些公开接口在WebMvcConfigurer配置类中排除拦截其他接口全部需要鉴权。这种做法的好处是所有受保护接口的鉴权逻辑集中在一个地方不需要在每个Controller里重复写校验代码。新加接口时也不用担心忘掉鉴权默认都走拦截器。4.2 订单场景的核心事务与状态流转订单模块是整个系统里业务逻辑最复杂的地方特别是认养或租赁类的订单不是简单的“生成一条记录”就完事的。以“用户发起认养”为例完整的后端处理流程是校验用户已登录判断地块是否存在且状态为“可认养”检查用户是否已经存在未完成订单防止重复下单生成唯一订单号设置订单状态为“待支付”插入订单表同步将地块状态改为“认养中”防止别人在支付期间再下单。这些操作要么全部成功要么全部回滚不能出现“订单生成了但地块状态没更新”这种数据不一致情况。SpringBoot里直接给Service方法加上Transactional注解是最高效的解决方案。Transactional会把整个方法包在数据库事务里任何一个SQL执行异常都会触发全部回滚。实际开发中有一个特别容易踩的坑Transactional默认只回滚RuntimeException如果你在方法里捕获了异常并且没有继续向上抛事务是不会回滚的。所以事务方法内部不要乱catch异常就算要catch也要在catch块里手动TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()或者在异常处理后重新抛出。订单状态流转建议直接在Service中维护一个清晰的状态机。比如“待支付→已支付→进行中→已完成”以及“待支付→已取消”每次状态变更前都要校验前置状态是否符合预期。这个状态机的代码不复杂但它能从根本上避免订单状态错乱的问题比如客户已取消但系统还在走支付流程。这里给出订单确认接口的Service层核心代码示意Transactional(rollbackFor Exception.class) public CreateOrderResponse createOrder(CreateOrderRequest request, Long userId) { Farmland farmland farmlandMapper.selectByIdForUpdate(request.getFarmlandId()); if (farmland null || farmland.getStatus() ! 1) { throw new BizException(地块不存在或已被认养); } Long count orderMapper.countActiveOrder(userId, farmland.getId()); if (count 0) { throw new BizException(您已存在该地块的进行中订单); } FarmlandOrder order new FarmlandOrder(); order.setOrderNo(generateOrderNo()); order.setUserId(userId); order.setFarmlandId(farmland.getId()); order.setAmount(farmland.getPrice()); order.setStatus(OrderStatus.WAIT_PAY.getCode()); order.setStartDate(request.getStartDate()); order.setEndDate(request.getEndDate()); orderMapper.insert(order); farmlandMapper.updateStatus(request.getFarmlandId(), 2); return new CreateOrderResponse(order.getOrderNo()); }注意selectByIdForUpdate这个写法它是SELECT ... FOR UPDATE语句对应的Mapper方法目的是把这一行数据锁定防止并发情况下两个用户同时下单同一地块。MySQL的InnoDB行锁在这里起了关键作用这也是订单类系统不能省略的一步。如果不加锁并发压力上来时同一块地可能被卖出去两单。4.3 MyBatis的关联查询、动态SQL与缓存使用MyBatis在乐享田园系统中的大部分工作都是编写Mapper接口和XML。订单列表页需要展示订单关联的地块名称和封面图这时候用关联查询最直接。farmland_order表左连接farmland表一次性查出订单和地块信息避免在Java代码里循环查询每条订单后再查一次地块那样会产生典型的N1查询问题。动态SQL也是MyBatis的亮点。后台地块管理页经常需要按分类、按状态、按名称模糊搜索组合查询在XML里用where配合if标签可以动态拼接SQL条件非常方便。相比在Java代码里拼接SQL字符串MyBatis的做法既安全又易读。再说到MyBatis缓存很多面试题和文章都在讲一级缓存和二级缓存。一缓存是指同一个SqlSession多次执行相同SQL时直接返回缓存结果二级缓存是跨SqlSession的Mapper级别缓存。实际项目中我的建议是默认关闭二级缓存因为管理系统里数据变更频繁开二级缓存后很容易出现“改了数据库但查询还是老数据”的问题处理起来非常麻烦。一级缓存默认开启但也要注意多表关联查询时如果两张表的缓存不一致也可能出现脏读。在订单这样写操作频繁的场景缓存主要靠数据库索引来支撑而不是靠MyBatis缓存。真正需要缓存的热点数据比如首页轮播图和基础配置类信息用Spring的Cacheable注解加一个内存缓存就足够应付绝大多数场景了。4.4 分页查询与统一返回结构列表页分页是所有管理系统躲不开的功能。SpringBoot MyBatis整合PageHelper是目前最常见的方案。用法很简单查询前调用PageHelper.startPage(pageNum, pageSize)接下来执行的第一次Mapper查询就会自动带上LIMIT并把总记录数塞进PageInfo里。PageHelper.startPage(pageNum, pageSize); ListFarmlandVO list farmlandMapper.selectWithBaseAndCategory(condition); PageInfoFarmlandVO pageInfo new PageInfo(list);这样前端要的分页数据就齐了pageInfo.getList()是当前页数据pageInfo.getTotal()是总记录数。需要注意的是PageHelper只有在紧接着的第一次查询时生效所以startPage和Mapper查询之间不要插入任何其他SQL操作否则分页会作用到错误的查询上。统一返回结构也非常重要。我的习惯是封装一个ResultT对象字段包括code、message、data。成功时code是200业务异常时code自行约定比如500或600前端根据code做不同提示。这么做的价值在于整个系统的前后端数据交互格式高度一致前端封装Axios拦截器时只需处理一种结构不用每个接口单独去判断返回格式。5. Vue前端落地从环境配置到动态页面5.1 Vue环境的安装与工程初始化前端部分首先要解决环境问题。Vue 3项目的本地开发需要提前装好Node.js。安装Node.js后脚手架工具Vite就可以通过npm使用了。我见过太多新手在安装依赖时卡住这里有一个非常实用的建议npm安装依赖慢或者频繁报错时检查一下是否把npm镜像源切换到了国内源这能让依赖安装速度快出好几倍。创建项目时用一行命令就可以完成npm create vitelatest lexiang-tianyuan -- --template vue这个命令会创建一个以Vue为模板的新前端工程。然后进入目录安装依赖cd lexiang-tianyuan npm install npm run dev这里要提一个真实开发中很常见的场景打开别人的项目源码时npm install后经常遇到依赖版本冲突或运行报错。我的排查思路是看package.json里的依赖声明和Node版本是否匹配Vue 3 Vite一般要求Node 18及以上。如果安装时出现ERR_OSSL_EVP_UNSUPPORTED这类OpenSSL错误大概率是Node版本和webpack老版本不兼容最简单的方案是升级Node或调整构建工具的版本组合。5.2 路由设计、菜单权限与页面骨架前端工程结构我习惯按功能划分目录。src/views存页面src/components存公共组件src/api存接口调用src/router存路由src/utils存工具函数。页面组件按业务命名比如farmland列表页、orderList订单页、dashboard后台首页、login登录页。路由要考虑权限控制。乐享田园系统角色不同能看到的页面也应该不同。普通用户登录后看到的是个人中心、我的订单等后台管理员看到的是地块管理、用户管理、订单管理、公告管理。实现方式是在路由配置里给每个页面加meta字段标记需要的角色在路由守卫中判断当前用户的角色是否匹配不匹配就重定向到无权限页。router.beforeEach((to, from, next) { const token localStorage.getItem(token) const userInfo JSON.parse(localStorage.getItem(userInfo) || {}) if (!token to.path ! /login) { next(/login) } else if (to.meta.roles !to.meta.roles.includes(userInfo.role)) { next(/403) } else { next() } })这段路由守卫是前端权限控制的核心。不过要提醒一点前端的路由守卫只是改善体验真正的权限校验必须以后端接口鉴权为准否则任何人通过接口工具直接调用就能绕开页面权限。所以前后端都要做权限控制两者不是替代关系而是互补关系。页面的整体布局用后台管理框架最常见的模式左侧是菜单栏顶部是用户信息和退出按钮右侧是内容区。菜单可以根据路由自动生成也可以手写一份管理后台的菜单数组。如果基于Vue Router动态生成菜单可以进一步通过后端把菜单权限返回给前端实现真正意义上的“动态路由”不过对于中小型系统简单的前端菜单控制已经足够。5.3 Axios封装、跨域与Token携带前端调用后端接口必须使用Axios但我不建议在每个组件里直接写axios而是统一封装。原因很现实统一封装后所有请求的baseURL、请求头、错误处理都只维护一份后期后端地址变了只需要改一个地方。封装的要点有三个。第一请求拦截器统一在headers中携带Tokenaxios.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config })第二响应拦截器统一处理后端返回结构。如果code不是200直接弹出对应的错误消息如果code是401清除登录信息并跳转登录页。第三所有请求统一用封装的函数比如request.get(url)、request.post(url, data)不要在业务代码里直接设置请求头或处理异常响应。跨域问题在前端开发时一定会遇到。本地开发时Vite默认跑在5173端口后端接口跑在8080端口浏览器会因为同源策略拦截跨域请求。解决办法有两个开发环境在Vite配置里设置server.proxy把/api代理到后端地址生产环境则因为前端静态文件已经打包进SpringBoot接口和页面同源跨域问题自动消失。Vite代理配置大约长这样server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端请求/api/user/login时实际会被代理到http://localhost:8080/user/login既解决了跨域又让前端代码里不用写死后端地址。6. 打包部署与常见问题排查6.1 前端打包放进SpringBoot里的正确姿势前后端都开发完成后接下来就是部署环节。把Vue项目打包成静态文件然后直接放进SpringBoot项目里一体化运行是目前中小型系统最省事的部署方式。具体操作分几步先在前端工程目录执行npm run build成功后生成dist目录。然后把dist目录下的所有文件复制到SpringBoot项目的src/main/resources/static目录下。最后重新打包SpringBoot项目并启动浏览器访问http://localhost:8080就能直接看到整个系统的页面。这里有几个坑必须提前预防。第一Vue的路由如果使用的是history模式访问非首页路径时后端需要做一些处理否则刷新页面会报404。最简单有效的处理方式是在后端写一个转发Controller或使用WebMvcConfigurer把非接口路径的请求转发到index.html。如果前端用hash模式就不会有这个问题代价是URL中会带一个#号。对于管理系统我建议直接使用hash模式省心且完全不影响使用。第二前端请求后端接口时如果代码里硬编码了http://localhost:8080这样的完整地址部署后一旦换了端口或域名就会请求失败。所以生产环境和开发环境最好分开配置环境变量或者所有接口都走/api前缀相对路径由前端的代理和后端路由统一处理。第三把静态文件复制到SpringBoot后需要注意缓存问题。因为静态文件都打进了JAR包里浏览器可能缓存旧版本导致页面更新后用户看到的还是老界面。一般处理方式是修改静态资源文件名加哈希或者在Controller中配置静态资源禁用缓存。Vite默认生成的构建文件名已经带哈希这个坑基本被友好避开了。6.2 数据库在Windows和Linux上的安装与配置要点MySQL的数据要在项目跑起来之前准备好。如果你的环境是Windows 10或Windows 11下载MySQL 8.0安装包后一路Next安装即可。但有几个关键步骤要注意安装时要记住root密码安装完成用命令行或可视化工具验证连接。Linux服务器上安装MySQL一般用包管理器安装。安装完成后第一件事是调整访问权限创建一个新的业务账号并授权给对应数据库而不是直接使用root账号跑项目。这件事很多人忽略但生产环境直接用root账号连接数据库是有安全隐患的。至少应该这样操作CREATE DATABASE lexiang_tianyuan DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER tianyuan_app% IDENTIFIED BY 你的密码; GRANT ALL PRIVILEGES ON lexiang_tianyuan.* TO tianyuan_app%; FLUSH PRIVILEGES;数据库连不上或者导入SQL失败百分之六七十都是因为字符集和账号授权问题。先确认库不是错误的字符集再确认账号有权限。6.3 常见问题速查开发过程中频发的坑在实际开发过程中有一些问题是出现频率极高的我把它做成一张速查表分享出来。问题现象原因分析解决方式前端报跨域错误端口不一致且未配置代理或跨域开发环境用Vite proxy生产环境打包进SpringBoot登录成功但后续接口401Token未设到请求头或拦截器未放行检查Axios请求拦截器和后端排除路径配置日期相差8小时数据库连接时区设置不对JDBC URL加serverTimezoneAsia/Shanghai事务不回滚catch了异常未抛出事务方法重新抛出RuntimeException或手动回滚分页数据错乱PageHelper.startPage和查询之间插入了其他SQL保证startPage紧接第一条查询语句前端打包后页面白屏静态资源路径以绝对路径开头构建时配置base为相对路径./点击菜单页面刷新404Vue history模式未作后端转发改hash模式或配置Controller转发中文乱码数据库字符集、连接字符集、页面编码不一致统一使用utf8mb4其中前端打包白屏这个问题我印象非常深。第一次把Vue项目构建后放进SpringBoot打开页面完全空白F12看一下控制台才发现静态资源全部引用的根路径/assets但应用部署在子路径或打包进JAR后目录结构变了资源加载失败。解决方式是修改Vite的base配置为/或相对路径./重新构建后又正常了。6.4 性能优化与后续扩展建议系统即便默认功能完整上线后仍然有性能优化的空间。乐享田园系统最值得优化的地方其实是数据库查询层。比如田园列表页如果每次进入首页都要查询所有地块和关联表数据数据库压力会随着数据量增长而明显变大。建议在列表查询中只返回必要的字段避免SELECT *并且对筛选条件和排序字段建好索引。首页的公告和轮播图是典型的读多写少数据可以通过Spring的Cacheable缓存起来把接口响应时间从几十毫秒降到几毫秒。这里要注意缓存失效策略公告一旦更新需要及时清理缓存否则用户永远看到旧公告。文件上传功能是这类田园系统的标配因为地块介绍、基地展示通常需要图片。SpringBoot实现文件上传非常简单接收MultipartFile后存储到本地指定目录或云存储即可。但上传目录要配置好访问映射否则前端拿不到图片URL。我是这样做的在配置类中把本地上传目录映射为/upload/**的访问路径这样前端可以直接通过http://localhost:8080/upload/xxx.jpg访问图片。如果将来用户量增大数据库瓶颈出现可以先通过MySQL读写分离缓解压力再考虑引入Redis缓存热点数据。如果业务量进一步攀升再把订单模块拆出来单独部署。这一系列演进路径都依赖于当初代码的分层是否清晰所以现在打好地基非常重要。7. 这套系统用到的一个容易被忽视的细节日志与异常处理写代码时大家最关注功能但其实日志和异常处理才是线上排查问题的基础设施。乐享田园系统里我习惯使用SLF4J的日志框架在每个Service实现类里通过LoggerFactory.getLogger()获取Logger对象然后在关键节点打日志。比如订单创建时打印入参用户ID、地块ID订单状态流转时打印原状态和新状态异常发生时不仅要打印异常堆栈还要打印当时的关键业务数据。这样出现问题后直接通过日志能定位到具体哪一步出了问题而不是靠猜。全局异常处理使用RestControllerAdvice统一处理。业务异常响应为错误码系统异常返回“系统繁忙”并记录完整异常日志。这样Controller里就不用到处写try-catch了功能代码干净很多。对我来说一个项目里日志打得是否到位几乎能直接反映开发者的熟练程度。部署完成后用java -jar启动SpringBoot应用时可以通过--server.port参数动态指定端口。定期查看日志文件或控制台输出就能第一时间发现数据库连接异常、接口报错等隐患。很多项目之所以线上问题难排查就是因为日志没打等用户反馈了才去逆推往往要花好几倍时间。写在最后的一点经验把整个乐享田园系统从零写完我最想强调的还是那句话技术栈只是工具真正决定项目成败的是业务理解和设计能力。拿到一个项目先花时间把角色、模块、表结构、状态流转理清楚后面写代码会顺畅得多。另外给正在做类似系统开发的同学一个非常明确的建议不要害怕报错报错信息是编程里最高效的老师。遇到看不懂的报错先读完整异常栈定位是前端、后端还是数据库的问题再针对性地搜索。绝大多数坑在第一眼看到报错时就已经给了提示只是很多人没耐心读到最后一行。如果你也正在做田园、农业、认养或租赁类管理系统把这篇文章里的表结构、事务处理、分页、打包部署这些环节照着落一遍你的项目完成度会明显提升一个档次。祝各位顺利把系统跑起来。
返回列表