
这套“Java SpringBootVue3MyBatis 同城上门喂遛宠物系统”最近在开发者圈子里讨论度不低。作为前后端分离的典型项目它麻雀虽小五脏俱全覆盖了从用户认证、订单流转到地图服务对接的完整链路。如果你是正在做毕业设计、想练手全栈项目、或者准备转Java开发岗的求职者这套系统的源码拆解值得你花半小时读下去。它解决的不是“怎么敲代码”的问题而是“一个真实的商业项目如何组织代码、设计表结构、安排业务流程”的问题。1. 项目概述与功能全景1.1 核心业务场景先把这个系统到底做了什么说清楚。同城上门喂遛宠物本质上是一个本地生活服务的撮合平台。宠物主人有出远门、加班、出差的需求宠物没人管另一边是闲在家里的宠物保姆——通常叫“宠物师”或者“遛宠师”他们有时间、有经验、也愿意赚这份钱。平台要做的就是把这两类人匹配起来。结合源码来看这套系统的用户角色分得比较清晰角色核心诉求系统提供的核心功能宠物主人找人按时喂食、遛狗、陪玩发布服务需求、下单、在线支付、实时查看服务进度宠物师接单赚钱、展示专业度接单、展示服务项目、上传服务记录照片/备注管理员保证平台有序运转订单审核、用户管理、服务类目管理、数据统计这里有个容易被忽略的点很多类似系统会把“宠物师”做成一刀切的统一角色但真实场景里宠物师也可能有专职和兼职之分服务技能也分“只遛狗”“只喂猫”“上门照顾爬宠”等。我看了源码的表结构它通过**服务类目表service_category**把服务类型做成了动态配置这意味着运营方可以在后台随时增删服务项目不需要改代码。这一点很加分说明作者是有真实业务运营思维的。1.2 整体架构设计技术栈是标准的前后端分离后端SpringBoot MyBatis前端Vue3 Element Plus或Vant取决于用户端是移动端还是PC端数据库MySQL。从前后端交互方式来看是典型的RESTful API JSON数据传输。后端的包结构大致是com.petcare ├── controller // 接口层 ├── service // 业务逻辑层 │ └── impl // 业务实现 ├── mapper // MyBatis数据访问层 ├── entity // 数据库实体 ├── dto // 数据传输对象 ├── vo // 视图对象 ├── config // 配置类 ├── utils // 工具类 └── common // 通用返回、异常处理这种分层是Java后端最常见的规范**Controller只做参数接收和结果封装不写业务逻辑Service层承载核心业务Mapper层只负责和数据库打交道。**对于新手来说最难理解的就是“为什么Controller不能直接写逻辑”。打个比方把餐厅比作一个系统Controller是大堂经理Service是后厨Mapper是仓库管理员。大堂经理可以收菜单接收参数、传菜返回结果但你不能让大堂经理钻进后厨去炒菜。一旦菜品出问题业务异常你总得知道是哪个环节出的问题——职责分明才好排查。前端方面这个项目用Vue3 Vite构建配合Vue Router做路由管理、Pinia做状态管理。看点在于Composition API组合式API的用法。现在Vue3已经是主流面试时几乎必问Composition API和Options API的区别。这套源码里对组合式API的使用可以作为参考范例——比如把“订单查询”相关的响应式状态和函数都封装在一个useOrderList()组合函数里组件里只用调用这个函数拿数据代码的复用性和可读性比Options API时代高了一大截。2. 为什么选这四件套技术选型的底层逻辑2.1 前后端分离的价值先说“前后端分离”这个概念。市面上很多学员项目号称前后端分离实际上后端返回HTML页面或者前端静态文件直接丢进后端项目里启动这都不算真正的分离。这套系统是真正的分离**前端工程Vue3项目通过Vite启动在5173端口后端SpringBoot启动在8080端口前后端通过HTTP请求通信部署时也是分开部署的。**为什么这么做我根据自己的经验总结了三个核心原因第一团队协作效率。前后端分离后前端开发只需要根据后端给出的接口文档Swagger或者YApi来Mock数据不必等后端代码写完才能开工后端也只需要保证接口返回数据格式正确不用关心前端长什么样。两边开发速度都明显加快。第二独立部署和扩展。系统访问量大了之后前端静态资源可以扔到Nginx或者CDN上减轻后端服务器压力后端接口可以水平扩展挂多台机器做负载均衡。如果前后端耦在一起你只能整包扩展资源浪费很严重。第三多端适配。现在做业务系统往往不只有一个PC后台还要有小程序、App、H5。后端接口做一次所有端都能共用同一套数据接口。这套系统里用户端和后台管理系统可能是两个不同的前端工程但共用同一个后端这就是前后端分离的最大价值。2.2 SpringBoot MyBatis为什么是这个组合SpringBoot在Java后端中的地位不用多说。它的核心优势是“自动配置”和“约定优于配置”。以前用SSMSpringSpringMVCMyBatis搭建项目光是配置就要写一堆XML文件而且每个项目配置基本一样繁琐又容易出错。SpringBoot把这些固定动作做成自动配置——你引入一个spring-boot-starter-web依赖内置的Tomcat直接就启动了一个RestController就能开始写接口。这在项目开发中节省了大量时间也让Java后端开发的门槛大幅下降。再说到MyBatis。为什么不用Spring Data JPA这里有一个很关键的区别必须讲清楚**JPA是面向对象的思维MyBatis是面向SQL的思维。**如果业务表格很简单、关联关系少JPA用起来特别舒服因为框架帮你自动生成SQL。但这种项目的订单查询往往涉及多表关联——订单表、用户表、宠物表、服务类目表、地址表查询条件还动态变化按城市、按服务类型、按订单状态用JPA去拼接动态SQL会很难受。MyBatis原生支持动态SQL标签if、where、foreach写复杂查询时直截了当SQL是什么样一眼就能看清性能调优也方便。另外MyBatis还有个容易被新手忽略的优势——XML和注解两种方式并存。简单SQL用注解直接写在Mapper接口上复杂SQL写在XML里与Java代码解耦。这套系统里的订单列表查询接口就是典型的复杂SQL用了where标签和if标签做动态条件拼接如果你打开源码里的OrderMapper.xml看一眼马上能理解那种“一张表一个mapper按需拼接SQL”的整洁感。2.3 Vue3为什么不用Vue2Vue3首发的版本是2020年9月到现在已经有大量生产环境验证。对于新项目来说用Vue2的理由已经越来越少。Vue3的Composition API解决了Vue2中Mixins带来的命名冲突和逻辑不清晰问题用ref和reactive处理响应式数据比Vue2的data对象灵活得多Vite的冷启动速度比Webpack快了一个数量级——一个中大型前端项目Webpack冷启动可能要30秒往上Vite基本3秒以内。这套系统用到的Vue3技术点值得照着敲一遍组合式APIscript setup语法糖省去一堆export default代码响应式APIref处理基础类型和对象reactive处理深层次对象computed处理派生状态路由守卫因为系统分“用户端”和“管理端”需要根据登录状态和角色做路由拦截——没登录跳登录页角色不对跳403页Pinia状态管理存储用户token、用户信息和角色信息刷新页面后数据不丢其中我最推荐学习的是组合式函数的封装方式。源码里通常会有一个src/composables/目录里面放着类似useOrderList、useUserInfo这样的函数。组件里只需要const { orderList, loading, fetchOrders } useOrderList() fetchOrders()一行代码就把订单列表的数据加载、加载状态、错误处理都从组件里抽出去了。这种写法在团队规模扩大之后尤其重要——因为它强迫你把逻辑和UI解耦逻辑可以独立测试UI组件可以保持纯粹。2.4 MySQL数据如何持久化MySQL至今仍是中小型业务系统的首选数据库原因很简单成熟稳定、生态完善、团队招人容易。这套系统的表结构大概包含这些核心表user—— 用户表含宠物主和宠物师用role字段区分pet—— 宠物档案表service_order—— 服务订单表service_category—— 服务类目表address—— 地址表payment—— 支付记录表order_comment—— 订单评价表从表的命名可以看出来作者遵循了基本的数据库设计规范**单词小写、下划线分隔、主键用id、外键用xxx_id结尾、所有表都带上create_time和update_time字段。**这种规范在真实公司里几乎是强制要求因为维护成本低老程序员接手新代码时能一眼看懂。这里我必须提醒一句**设计数据库表时时间字段类型优先选择datetime不要用timestamp。**虽然timestamp在2038年之前够用而且能自动转换时区但如果你将来做多地域部署时区转换的坑能烦死你。datetime存的就是一个字面值备注里写明“北京时间”永远没有问题。3. 数据库设计与核心表结构拆解3.1 用户与宠物模块设计思路用户表是整个系统的地基。它里面有一个role字段值是1代表宠物主人2是宠物师。有些同学喜欢拆两张表一张pet_owner一张pet_sitter这样看起来“更规范化”实际使用中发现两个角色的公共字段重复度极高而且将来如果出现“同一个用户既是宠物主又是宠物师”的场景拆表就会非常尴尬——你得在两套表里都插入一条数据还要处理同步问题。用一个通用user表加role字段一个用户的多个身份切换起来非常顺滑。宠物表的设计要点在于“建档”和“过敏信息”的联动。宠物主注册完账号后需要给每一只宠物建立档案包括昵称、品种、年龄、性别、体重、是否绝育、有没有攻击性、饮食禁忌等信息。这些字段看似琐碎但其实直接影响订单匹配的效率——比如有的宠物师不接大型犬如果订单页面可以直接看到宠物品种和体重宠物师就能快速判断接不接单省去反复沟通的麻烦。3.2 订单表业务状态流转的枢纽订单表是整个系统最核心的表我把它单独拿出来讲。它的字段大致包括字段名类型说明idbigint主键order_novarchar(32)订单编号业务唯一owner_idbigint宠物主人IDsitter_idbigint宠物师IDpet_idbigint宠物IDservice_type_idbigint服务类目IDstatustinyint订单状态service_timedatetime预计上门时间service_durationint服务时长分钟address_idbigint服务地址IDtotal_amountdecimal(10,2)订单总金额remarkvarchar(500)备注create_time / update_timedatetime创建/更新时间重点说订单状态。这里用tinyint存状态码而不是直接存“待接单”“进行中”这种中文原因有两个一是数字占空间小、查询速度快二是状态码和状态描述分离将来修改状态文案不需要动数据库。整套系统的订单状态流转是待付款 → 待接单 → 已接单 → 服务中 → 已完成 → 已评价 ↘ 已取消状态流转的实现方式是严格的每个状态变化都通过Service层的业务方法完成并且方法开头会校验当前状态是否允许跳转到目标状态。比如“已取消”订单不允许再变成“已接单”“已完成”订单不允许再变回“服务中”。这种状态机校验写起来不复杂但能挡住很多脏数据。这里有一个非常实用的经验**订单编号生成不要用数据库自增ID要用“日期随机数用户ID尾号”这种业务可读编号。**自增ID直接暴露在前端不仅让竞争对手能预估你的业务量还会被有心人用来遍历订单数据。这套系统的order_no大概是20250101120000123这种格式前段是日期时间中间几位是随机序列尾部是对应用户ID的后几位。这样即使不关联查询运营人员在后台看到订单号也能知道大概的生成时间和用户特征。3.3 为什么添加“服务地址”单独表地址可能是一张容易被忽略的表但在这类同城服务系统里地址不仅承载“门牌号”信息更是整个推荐匹配策略的数据基础。地址表里至少要有省市区编码、详细地址、经度、纬度四个核心字段。经纬度字段很关键。宠物师接单的时候系统需要计算宠物师所在位置和宠物主家的距离超过一定范围比如10公里就直接不展示订单因为宠物师不可能为了遛一次狗跨半个城市跑一趟。距离计算通常有两种方案第一种是MySQL直接算用ST_Distance_Sphere函数MySQL 5.7及以上支持一条SQL就能按距离排序。第二种是应用层算好再查询先用经纬度算出距离再作为参数传入查询。源码里大概率是第一种方案因为实现简单、数据不需要额外同步。但你要是做的是高并发系统建议把经纬度改成Redis GEO结构配合地理位置索引性能会好很多。对这个系统来说MySQL自带的空间函数完全够用不必为了炫技而引入新组件。4. 后端核心业务功能与接口实现4.1 用户认证与授权JWT怎么做系统的认证方式用的是JWTJSON Web Token。刚学后端的人容易把“登录”想成只有“校验用户名密码”一步其实完整的流程是用户提交手机号验证码或密码到后端后端校验通过后生成一个JWT返回给前端前端把JWT存在localStorage里每次请求在Authorization头带上后端通过拦截器解析JWT确认用户身份和角色对于管理端接口还会再校验一次角色是否匹配比如只有role0的管理员才能访问用户管理接口JWT的三个部分Header、Payload、Signature前两部分是Base64编码可以直接解码看到内容最后一部分是签名用于防止内容被篡改。我在这个项目里特别想提醒的是不要把敏感信息直接放Payload里。比如用户手机号放了就能看到密码更不能放因为就算有签名Payload本身是明文可见的。正确的做法是只放userId和role其他信息每次请求时再查数据库。虽然多一次查询但安全性和系统扩展性都好得多。另一个容易被忽略的安全点是密码存储。这个系统里用户表存的是bcrypt加密后的密码哈希。为什么不用MD5因为MD5是“摘要函数”设计目标就是快你算得快攻击者也算得快彩虹表一查就出结果。BCrypt算法内置随机salt同一个密码每次加密结果都不同且刻意设计得比较慢暴力破解成本高得多。4.2 订单流程从下单到完单的完整闭环订单模块是整个系统的业务核心。从源码里看一个下单请求从前端发起后后端的处理逻辑大致是**第一步参数校验。**前端可能因为bug或者用户反复点击发送了非法数据后端必须校验。比如服务时间不能是过去时间宠物ID必须属于当前登录用户订单金额不能是负数。**第二步扣减库存/校验状态。**这里库存指的不是实物库存而是宠物师的“可接单时间”。如果宠物师在某个时间段已被其他订单排满新的订单就不能再接。实现方式上源码使用了悲观锁或者乐观锁——最常见的做法是在下单时先SELECT ... FOR UPDATE锁定宠物师的排班记录确认无冲突后插入新订单。**第三步生成订单编号并保存。**这个上面已经说过了编号生成用业务规则不依赖自增。**第四步调用支付接口。**如果是对接真实支付这里要调用微信支付/支付宝预下单接口把支付参数返回前端前端拉起支付组件。如果是演示项目通常用一个“模拟支付”接口直接置为已支付。**第五步发送通知。**下单成功后需要通知宠物师有新的可接订单。源码里如果是简化的实现可能是轮询订单列表如果做了消息推送会有WebSocket或者第三方推送类似极光推送的接入。这里牵扯到一个现实问题宠物师端必须实时感知新订单实现方式的选择影响很大。轮询的问题在于延迟和无效请求多WebSocket适合即时通讯场景但宠物师如果App不在前台连接会断开这时候推送也需要兜底方案。考虑到这套系统的定位源码中大概率用了轮询页面刷新提醒这也是可接受的简化方案。再往下走宠物师接单这个动作同样有并发风险多个宠物师同时抢同一个订单怎么办正确做法是在更新订单状态时加上WHERE sitter_id IS NULL这个条件。如果宠物师A先提交更新成功但影响行数为1宠物师B再提交sitter_id已经有值了更新影响行数为0响应“订单已被他人接走”。这是最简单的乐观锁实现不需要额外引入Redis分布式锁性能也够用。接单后就是服务履约阶段。宠物师上门后系统提供一个“开始服务”按钮点击后状态变为“服务中”服务结束时点击“完成服务”订单状态变成“待评价”。这个过程看似简单但真实运营中还有个更细的需求宠物师在服务过程中可以上传服务照片、备注喂食情况、甚至发一段短视频。这些内容要存到订单明细表将来售后有争议时调取证据链。这套系统的表结构里如果没做“服务记录表”那至少有一个“订单追踪表”保存状态变更的时间和操作人ID。4.3 MyBatis动态SQL实战订单查询怎么写订单列表页是用户端和管理端都会用到的功能但查询条件不一样。用户端可能是“只看我发布的订单”管理端则是“按城市/按状态/按宠物师名称筛选”。如果每个查询条件都写一个不同的接口和Mapper方法那代码冗余度会非常高。MyBatis的动态SQL就是为这种场景设计的。源码里OrderMapper.xml通常长这样select idselectOrderList resultTypecom.petcare.vo.OrderVO SELECT o.*, u.nickname AS owner_name, p.name AS pet_name, c.name AS service_name FROM service_order o LEFT JOIN user u ON o.owner_id u.id LEFT JOIN pet p ON o.pet_id p.id LEFT JOIN service_category c ON o.service_type_id c.id where if testownerId ! null AND o.owner_id #{ownerId} /if if testsitterId ! null AND o.sitter_id #{sitterId} /if if teststatus ! null AND o.status #{status} /if if testcityCode ! null and cityCode ! AND o.address_id IN ( SELECT id FROM address WHERE city_code #{cityCode} ) /if /where ORDER BY o.create_time DESC LIMIT #{offset}, #{pageSize} /select这个SQL的设计有三个值得学习的点第一where标签自动处理条件拼接。第一个条件前面不用写WHERE也不会报错因为MyBatis会自动判断有没有条件语句没条件时整个WHERE都不会拼进去。第二LEFT JOIN带出关联表信息。订单列表页要显示宠物名和主人昵称这是典型的多表关联场景。如果你把所有信息都冗余到订单表里维护成本会高如果只在列表页做二次查询又会造成N1问题。LEFT JOIN一次查出结果是常规做法也是最优解。第三分页用LIMIT offset, pageSize。这种物理分页对MySQL来说效率尚可。有人会说应该用LIMIT #{pageSize} OFFSET #{offset}这是PostgreSQL的语法MySQL里必须用LIMIT offset, pageSize的格式别搞混了。4.4 前后端交互中的VO与DTO区别刚接触企业级项目的同学经常把entity、VO、DTO混为一谈。在这套系统里它们的职责其实是比较清晰的entity和数据库表字段一 一对应的实体类DTO接收前端请求数据的对象比如登录请求DTO、下单DTOVO返回给前端展示用的对象比如用户登录后返回的用户信息VO为什么要区分因为三层之间的数据“长得不一样”。比如新增订单时前端传来的参数是petId、serviceTypeId、serviceTime、addressId、remark这些和service_order表的字段能对上可以用entity直接接收。但返回订单列表时前端要的不只是订单表的字段还有宠物名称、宠物师昵称、服务类目名称如果还用entity返回前端拿不到这些关联信息如果硬把entity加上这些字段那entity和表结构就不对等了。**规范的措辞是数据库表结构变动导致的entity改动不应该影响到接口的出参格式。**所以你会看到源码里controller返回类型几乎都是ResultT其中T是VO对象。这种封装带来的好处是即使未来把订单表的字段改了只要VO的字段没变前端代码一行都不用动。5. 前端核心页面与功能实现5.1 用户端移动优先的体验设计用户端是给宠物主人用的场景决定了它必须针对手机屏幕做适配。Vue3 Vant移动端组件库或者Vue3 Element PlusPC适配取决于作者的目标——看标题是“同城上门”大概率会偏向移动端布局也就是手机上能直接在微信里打开H5页面完成下单。用户端的核心页面大概有这四类首页服务列表页顶部是城市定位下面是服务类目的卡片比如“上门喂猫”“上门遛狗”“宠物陪伴”。每一个类目卡片都有图标、标题、价格和简介。点击进去是详细的服务说明页底部一个大大的“预约”按钮。下单页选择宠物、选择服务时间、填写地址、补充备注。这里有个细节用户下单时如果宠物档案还没建系统会先引导去建档案而不是直接报错。这种“前置条件未满足时引导用户解决”的产品思维值得前端开发学习——不是所有交互都靠弹窗报错有时候一个温柔的引导入口就能提升不少转化率。订单列表页用户能看到所有历史订单按状态分成“待接单”“服务中”“已完成”等标签页。状态变化时列表页要能实时刷新。如果用了WebSocket或者轮询前端轮询的时间间隔要注意——5秒一次比较合适太频繁浪费后端资源太慢体验又跟不上去。个人中心基本信息、宠物档案管理、地址管理、退换货售后、客服入口等。从Vue3的实现角度看这套系统前端的亮点在于状态管理是严谨的。用户token存在Pinia里每次请求通过axios拦截器自动加上Authorization头如果接口返回401token失效或过期前端自动清空用户信息并跳转登录页。这个“统一处理401”的逻辑是很多半吊子项目都漏掉的地方——他们只会在单个请求里写catchtoken过期后用户看到一个红红的报错而不是被温柔地带回登录页。5.2 宠物师端接单与履约效率宠物师端的页面逻辑相对简单但操作频率更高。核心是订单列表和订单详情。订单列表默认按照距离和发布时间排序宠物师能看到“单主宠物类型”“服务时间”“预计收入”。接单按钮在列表页直接露出是出于效率考虑——宠物师刷单就像外卖骑手刷单一样看中的就是信息一目了然、操作一步到位。订单详情页包含宠物档案、服务地址、主人备注还有联系主人的入口通常是虚拟号码拨号。服务开始后宠物师需要上传服务照片和填写喂食记录。这部分做得好不好直接决定了平台对宠物主的可信度。我见过不少系统在“服务记录”这块做得很粗糙比如只允许传一张照片、备注字数上限太短等其实需求方很在意“你去喂了没有猫的状态怎么样”多照片文字描述是底线要求。5.3 管理后台Vue3 Element Plus的数据看板管理后台用Vue3 Element Plus是最成熟的组合。页面一般包括仪表盘今日订单量、营收总额、新增用户数、服务完成率用户管理用户列表、状态禁用/解禁宠物师管理资质审核身份证、健康证、接单量统计订单管理条件查询、异常订单处理取消、退款服务类目管理增加/编辑/下架服务类目管理后台的前端技术点集中在表格组件和表单校验上。Element Plus的el-table配合自定义列渲染基本能覆盖所有列表需求表单用el-form的rules配置校验规则。这一块对前端求职者来说是比较容易上手的方向照着源码写一遍基本就把后台管理系统的常见套路摸清了。6. 部署上线与常见问题排查实录6.1 本地跑通这套系统的完整流程拿到源码之后很多人卡在第一步——跑不起来。我把自己踩过的坑整理成一份“一步一图”的启动清单你可以直接照着做。后端启动步骤安装JDK 8或11同时配置JAVA_HOME环境变量。命令行输入java -version验证版本没问题再继续。安装MySQL 5.7创建数据库比如pet_care_db字符集一定要选utf8mb4。字符集是很多人忽略的坑如果你用默认的latin1存中文数据的时候会变成乱码。utf8mb4兼容utf8且支持emoji是MySQL 5.7以上的标准选择。执行源码中提供的sql/init.sql脚本导入表结构和初始数据。注意脚本里可能有多个用户角色的初始账号记录下来方便测试。打开application.yml或application-dev.yml把数据库连接信息改成你自己的spring: datasource: url: jdbc:mysql://localhost:3306/pet_care_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver这里特别提醒serverTimezoneAsia/Shanghai一定要配否则会报时区错误——MySQL 8.0默认时区和本地不一样Java连接时会报The server time zone value йʱ is unrecognized。 5. 在项目根目录执行mvn spring-boot:run看到Started Application日志就说明启动成功。前端启动步骤安装Node.js 16然后再全局安装pnpm或直接用npm。在frontend目录执行npm install。如果安装慢可以把镜像源换成国内源npm config set registry https://registry.npmmirror.com。执行npm run dev启动开发服务器浏览器访问http://localhost:5173。如果后端接口报跨域错误检查项目里是否配置了跨域处理。SpringBoot的常见做法是加一个CorsConfig配置类允许http://localhost:5173的跨域请求。注意如果你把前端部署到Nginx而接口还是走后端8080端口需要配置Nginx反向代理把/api的请求转发到后端。这一步是前后端分离部署的必修课不建议跳过。6.2 开发中踩过的坑高频问题速查表我把这个项目开发过程中常见的问题整理成一张表每一条都是我自己实操中遇到过并排查解决的问题现象可能原因解决方法请求接口报404前端请求路径和后端RequestMapping不一致检查后端接口路径特别是带参数拼接的/user/{id}格式接口报401JWT过期或token未携带前端检查axios拦截器是否设置了Authorization头检查token是否过期中文乱码数据库字符集不是utf8mb4统一数据库、连接串、页面编码均为utf8mb4跨域报错前后端端口不一致未配置CORS在后端加CorsConfig过滤器允许前端源数据库连接失败驱动版本不匹配SpringBoot 2.x用com.mysql.cj.jdbc.DriverMySQL 5.7和8.0均可页面加载白屏前端路由或静态资源路径错误检查vite.config的base配置部署到子目录时需要设置base: ./订单列表数据不对动态SQL拼接条件写错打开MyBatis SQL日志查看实际执行的SQL其中最后一个问题值得展开说。调试MyBatis动态SQL最直接的手段就是打开SQL日志。在application.yml里加一行logging: level: com.petcare.mapper: debug这样控制台会把每个Mapper接口实际执行的SQL语句打印出来?占位符对应的参数也会一起打印。看到实际SQL你就知道问题是SQL本身写错了还是参数传错了。6.3 上线部署选什么这套系统的部署方案如果只有一台2核4G的云服务器最省资源的方案是前端构建完静态文件丢给Nginx托管后端打jar包用systemd服务保持后台运行MySQL也装在同一台机器上数据库定期备份Nginx配置的核心片段server { listen 80; server_name 你的域名; location / { root /var/www/pet-front/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里try_files $uri $uri/ /index.html;这行很重要——Vue Router如果用的是history模式刷新非首页路由时Nginx会直接404必须通过try_files把请求重定向到index.html交给前端路由接管。后端jar包启动建议用systemd管理而不是直接nohup java -jar。systemd的好处是开机自启、崩溃自动重启、日志集中管理。比较简单的服务配置是这样[Unit] DescriptionPet Care Backend Aftermysql.service [Service] ExecStart/usr/bin/java -jar /opt/pet-care/app.jar Restartalways RestartSec10 Userroot [Install] WantedBymulti-user.target把文件放到/etc/systemd/system/pet-care.service然后执行systemctl daemon-reload systemctl enable --now pet-care就完成了。7. MyBatis源码级调优从“能用”到“好用”7.1 一级缓存与二级缓存面试高频题“MyBatis缓存”在这套系统中也值得实践一下。MyBatis默认开启一级缓存SqlSession级别同一个SqlSession内执行相同SQL会直接返回缓存结果不查数据库。问题在于SpringBoot中每个Service方法通常对应一个SqlSession方法结束SqlSession就关闭了一级缓存在Spring环境中作用不大。二级缓存是Mapper级别的跨SqlSession共享。但这套系统不建议开理由是二级缓存需要每个实体类实现Serializable接口并且对热点数据的实时性要求很高的场景比如订单状态缓存了反而容易出问题——状态变了缓存还没刷新用户看到的是旧数据。这种项目对数据实时性要求高不如把缓存彻底关掉保证查询结果永远是数据库最新状态。7.2 分页插件的引入MyBatis自带的分页逻辑是LIMIT #{offset}, #{pageSize}听起来简单但问题在于如果你写复杂的动态SQLLIMIT位置稍微写错就会报错。这时候引入PageHelper插件是最省事的方案。它通过拦截器自动在SQL后面追加LIMIT语句并且自动生成SELECT COUNT(*)查询总条数。用法很简单PageHelper.startPage(pageNum, pageSize); ListOrderVO list orderMapper.selectOrderList(query); PageInfoOrderVO pageInfo new PageInfo(list);但这里有个坑**PageHelper是线程安全的吗**它的startPage方法使用了ThreadLocal存储分页参数如果后续有多个查询方法第二个查询会误以为也要分页。所以正确的用法是分页参数生效的那个查询方法后面必须紧跟这段查询代码中间不要插入其他Mapper方法。这个Peter的经验在面试中如果你能说出来绝对是个加分项。7.3 SQL性能排查工具生产环境如果你不确定某条SQL执行得慢不慢打开MySQL慢查询日志是排查利器。MySQL 5.7及以上配置方式SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1; SET GLOBAL slow_query_log_file /var/log/mysql/slow.log;这样执行时间超过1秒的SQL都会被记录下来。结合EXPLAIN语句分析有没有走索引、扫描行数多少、是否产生了临时表基本能把慢查询问题都定位出来。这套系统里最可能出现性能瓶颈的两个场景一个是订单列表的LEFT JOIN子查询。如果用户表数据超过100万条LEFT JOIN会把三张表的所有数据都取出来做关联非常慢。优化思路是先在大表上过滤条件比如只查近30天订单再JOIN小表。另一个是距离排序查询。如果用ST_Distance_Sphere它会扫描全表计算距离不走索引。这时需要把经纬度范围缩小先按经纬度粗筛出一个矩形范围比如经纬度上下浮动0.1度再在这个结果集里计算精确距离。这个优化能把查询范围从全表缩小到城市内的局部区域性能提升非常明显。8. 这套源码能学到什么我的横向总结如果你认真读完了上面每一段你会发现这套“同城上门喂遛宠物系统”核心技术点并不神秘但链条很完整——从前端交互到后端接口从数据库设计到部署上线每一步都有真实的工程决策痕迹。我最大的感受是**它不是一个“为了教学而教学”的玩具项目它的每个功能点都能对应到真实业务场景中的某个痛点。**比如宠物师接单的并发控制、订单状态机的严格流转、按距离匹配宠物师、前端401统一处理这些都是工作中天天会碰到的问题只是在培训教程里大多数都一笔带过了。如果你正在准备Java开发岗面试我强烈建议把这只项目吃透。面试官问项目经验时你可以不谈“我写了一个宠物平台”而是谈“我设计了一套订单状态机能自动拦截非法状态流转”“我用乐观锁实现了宠物师并发抢单的零超卖”“我把查询接口的VO和Entity分离避免表结构变动引发接口变更”——这些话术背后的设计思想一次性把简历上的项目亮点展现得清清楚楚。最后分享一条个人经验**不要停留在把系统的代码跑起来、页面能点动的阶段。**把源码里的每一个“为什么”问一遍——为什么订单状态用数字不用枚举字符串为什么用Composition API而不用Mixins为什么连表查询选择LEFT JOIN不用IN子查询——当你真的能把这些问题在自己的语言体系里重新讲一遍时而不是背词条这套源码才算真正属于你了。