
这两年做同城服务类项目的朋友越来越多尤其是宠物上门喂食、遛狗这类需求疫情后增长势头一直很猛。我手头刚好整理了一套完整可跑的前后端分离实现技术栈就是SpringBootVueMyBatisMySQL源码和部署流程都齐全。这篇文章我会直接把这套同城上门喂遛宠物系统的架构思路、核心功能拆分、数据库设计、关键代码实现到本地部署的完整链路讲清楚。不管是打算接私活、做毕业设计还是想自己创业跑通一个最小可行产品这套东西都能给你省下大量从零折腾的时间。先说清楚这套系统到底解决什么问题。宠物主人出差、加班、应酬的时候家里的猫狗没人管需要一个可信赖的人上门喂食、换水、铲屎、遛弯。系统要打通的是“主人下单—平台派单/抢单—服务人员接单—上门服务—确认完成—评价付款”这条完整闭环。技术上选前后端分离是因为这套业务天然分为“管理端用户端服务端”多角色场景Vue做页面交互SpringBoot提供接口MyBatis负责SQL层MySQL存业务数据工程结构清晰、对新手友好、也方便后期扩展小程序或者App。全文我尽量按实际动手顺序来讲从设计思路到表结构再到接口实现和前端页面最后是部署排坑。内容偏长建议收藏了慢慢看。1. 先搞清楚业务边界与系统角色划分1.1 上门喂遛宠物这事系统里到底要管哪些关键环节很多人一上来就写代码结果做着做着发现业务边界一团浆糊。我这里先帮大家把核心业务链路梳理清楚。一套同城上门喂遛宠物系统最核心的用户流程其实是这几条宠物主人在微信或网页端注册登录创建自己的宠物档案品种、年龄、饮食习惯、是否怕生、疫苗情况等主人发起服务订单选择服务类型上门喂食、遛狗、宠物陪玩、铲屎清洁等选择服务时间和地址系统根据地址和服务时间匹配可接单的服务人员也就是遛狗师/喂养师可以做成抢单模式也可以做成指派模式服务人员接单后按约定时间上门服务服务过程中可能需要拍照上传、打卡主人可以实时看到订单状态服务完成后主人确认验收进行支付和评价服务人员获得相应收入。这就涉及三类角色的权限区分普通用户宠物主人、服务人员接单方、平台管理员审核、管理、统计。如果只做一个“表加增删改查”的简单版本看起来功能能跑但一上线就会被各种状态流转和异常情况搞崩。比如订单已经派出去、服务人员临时有事取消怎么办主人下单后未支付订单超时怎么办服务人员接单和手机端刷新重复提交怎么办这些都是需要提前在架构层面想清楚的。这套系统的做法是把订单状态做成状态机再用前端按钮权限和后端接口鉴权双保险避免用户越权操作。数据模型上把用户、宠物、订单、服务项目、评价、支付流水拆成独立模块互相之间只通过ID关联做到业务数据可追溯。1.2 技术选型为什么是SpringBootVueMyBatisMySQL而不是其他组合这可能是很多同学纠结的第一个问题。有些朋友一上来就推荐微服务、Redis、RabbitMQ但实际上同城喂遛宠物这种业务规模在早期阶段用微服务纯属给自己找麻烦。选择SpringBootVueMyBatisMySQL这套组合核心逻辑是“投入产出比最高”。SpringBoot是目前Java后端开发事实上的标准框架内置Tomcat起步依赖管理方便社区资料多遇到问题搜索基本都能解决。Vue作为前端框架学习曲线平缓响应式数据绑定对订单状态这种频繁变化的场景非常友好。MyBatis做持久层比JPA更容易控制SQL尤其适合订单报表、多表联查这类复杂查询半自动化的特性也更容易排查线上SQL问题。MySQL则完全够用单机部署、事务支持完善、运维简单。还有一个很重要的原因是这套技术栈人才储备量大。找人接手、找开源代码、招实习生都容易项目不会因为某个人离职就死掉。之前我见过一个项目用了冷门ORM框架和自定义前端脚手架结果维护成本高得离谱踩坑都没地方问。做项目选型流行度和团队熟悉度本身就是重要指标。2. 数据库设计一张订单表撑起整个业务闭环2.1 核心表结构拆解用户表、宠物表、服务人员表数据库是一套业务系统的地基。地基没打好后面写多少代码都白搭。我按模块给大家拆一下这套系统的核心表结构实际源码里就是按这套模板初始化的。用户表sys_user是登录入口字段设计上要区分三类角色id主键自增用户名、密码BCrypt加密存储、手机号、头像地址role字段做角色区分0代表普通用户1代表服务人员2代表管理员状态字段1正常0封禁创建时间和更新时间所有表都保留这两个字段方便排查问题。宠物表pet_info是宠物主人侧的档案信息在这里要下点功夫因为这是上门服务安全性的第一道保障id、用户ID外键关联用户表、宠物名称、品种、年龄、体重是否接种疫苗、是否有攻击性、是否绝育这些字段在上门前必须展示给服务人员饮食习惯备注、用药备注比如每天几点喂药、紧急联系人电话宠物照片URL用于主人展示和订单详情展示。服务人员表service_worker单独拆出来的原因是服务人员除了基础用户信息之外还需要审核资质和服务评分id、用户ID关联用户表、真实姓名、身份证号脱敏存储、服务区域经纬度或区域码服务类型资质喂猫、遛狗、宠物护理、个人介绍、服务价格评分均值由订单完成后用户评价算出来审核状态0待审核1审核通过2驳回。平台必须审核服务人员资质这是底线。这三张表是基础。订单表、评价表、支付流水表在它们之上做业务流转。我特别强调一下身份证号这类敏感信息不要在数据库里明文存至少要做加密处理或者脱敏展示这是正规项目的基本素养。2.2 订单表与服务记录表状态机落地的关键订单表order_info是整个系统的核心。字段设计不能只想着“保存一次数据”还要想着“支撑整个订单生命周期”。核心字段至少覆盖id、订单编号业务上用来给用户看的不要直接用自增id用户ID、服务人员ID、宠物ID服务类型1上门喂食2遛狗3宠物陪玩4综合服务服务开始时间、结束时间、服务地址省市区详细地址经纬度订单金额、优惠金额、实付金额、支付方式订单状态这里重点讲一下状态定义0待支付、1待接单、2已接单、3服务中、4待确认、5已完成、6已取消、7退款中、8已退款备注、紧急联系电话、创建时间、更新时间。这个状态定义我是踩过坑的。第一次做类似项目时我把状态设计成“0未完成、1已完成”两个状态结果后面业务一扩展直接推倒重来了。订单状态字段千万不能省宁可多设计几个中间状态也不要用布尔值去代替。比如“服务中”和“待确认”如果合并成一个状态用户端和服务端看到的操作按钮就会乱掉。服务记录表service_record记录每次上门服务的动作明细算是订单表的附件id、订单ID、服务人员ID上门时间、离开时间、服务内容遛狗时长、喂食照片、宠物状态描述服务现场照片1、照片2、照片3用URL存储是否按时到达、是否异常反馈、用户确认时间。这套设计的好处是平台如果想做“服务质量回溯”从订单表和服务记录表就能拼出完整的证据链。以后用户投诉、保险理赔、服务人员绩效评估都有数据支撑。2.3 MyBatis映射文件怎么组织更清晰MyBatis在项目里的组织方式我建议按模块分文件不要所有SQL堆在一个XML里。这套系统的做法是mapper接口定义一个模块一个比如OrderMapper、UserMapper、PetMapper、EvaluateMapperXML文件按照对应的Mapper接口一一对应放在resources/mapper目录下简单的单表增删改查用注解直接写在接口上复杂动态查询写XML。举个订单查询的例子后端接口要支持用户按状态筛选自己的订单列表SQL要能动态拼条件。如果全写在XML里就是select idselectOrderList resultTypecom.example.pet.entity.OrderInfo SELECT * FROM order_info where if testuserId ! null and userId ! AND user_id #{userId} /if if teststatus ! null AND status #{status} /if if teststartTime ! null AND create_time gt; #{startTime} /if /where ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} /select注意排序字段和分页参数一定要用#{}预编译不要用${}直接拼接这能防SQL注入也能提高执行效率。MyBatis还有一个很实用的配置是驼峰命名自动映射数据库字段的snake_case可以自动映射到Java属性的驼峰命名减少了一堆resultMap手写工作量。3. 后端核心功能实现从登录鉴权到订单流转3.1 登录鉴权与角色权限控制这套系统的登录用的是JWTJSON Web Token实现逻辑不复杂但很实用。用户登录成功后后端生成一个加密token返回给前端前端存到localStorage或pinia/vuex里每次请求在请求头带上Authorization字段后端用拦截器校验token有效性。JWT有个特点是无状态的后端不用存session这在前后端分离架构下特别合适。但要注意token过期时间的设置我一般设置为2小时有效同时要求前端在token即将过期前调用刷新接口续期。不然用户正下单填到一半突然token失效跳登录页体验非常糟糕。权限控制这里要重点说。前端做路由守卫根据用户角色生成不同的菜单后端做接口权限校验拦截器里解析出当前用户角色判断是否能访问某个接口。两层都做防止有人绕过前端直接调接口。比如“服务人员接单”这个动作前端只有角色为1服务人员的用户才显示接单按钮后端在接收请求时也要校验当前登录用户角色确实是服务人员否则返回403。3.2 订单状态机与并发防重订单状态机是这个项目里最有含金量的部分。我给大家画一张状态流转的逻辑不画图用文字描述状态0待支付用户提交订单后进入支付成功后变1待接单超时30分钟未支付自动取消定时任务扫描状态1待接单服务人员看到可抢订单抢单成功后变2已接单状态2已接单服务人员到达现场点击“开始服务”变3服务中状态3服务中服务人员完成服务点击“完成服务”变4待确认状态4待确认用户确认服务无误变5已完成同时触发支付确认和评价任何非终态都有机会走6已取消或7退款中。这个状态机看着不复杂但实现的时候有个最容易出bug的点并发防重。想象一下多个服务人员同时抢同一个订单如果后端代码先查订单状态、再更新订单状态两个请求同时进来都查到待接单然后都执行更新订单就被两个服务人员同时接走了。解决办法是更新SQL里加上状态条件int rows orderMapper.updateStatusAndWorker( orderId, workerId, OrderStatus.WAIT_ACCEPT.getCode(), OrderStatus.ACCEPTED.getCode() ); if (rows 0) { // 抢单失败订单状态已被别人修改 throw new BizException(手慢了订单已被抢走); }这里利用数据库行锁和更新行数来判断是否抢单成功简单高效。我再补充一个细节数据库连接池配置上更新操作的事务隔离级别使用默认的READ_COMMITTED就够了不需要调成SERIALIZABLE否则并发性能会明显下降。定时任务处理超时订单推荐用Spring自带的Scheduled注解加一个开关配置每天凌晨扫描一次待支付超过30分钟的订单自动取消同时释放对应的服务时间段避免服务人员那边被无效订单占住排期。3.3 文件上传与图片存储上门喂遛宠物这种业务现场照片是重要凭证。服务人员要能拍照上传这里就要处理图片上传功能。我用的是本地存储方式配置一个上传目录SpringBoot接收MultipartFile后保存到磁盘同时把相对路径返回给前端前端拼上访问前缀展示图片。不推荐把图片存到MySQL的Blob字段里虽然也能跑但数据库会迅速膨胀、备份困难。正确做法是数据库只存路径图片文件放磁盘或云对象存储。如果线上部署可以把上传目录配置成云存储的挂载目录或者换成对象存储SDK工程改动都相对可控。另外图片一定要做大小限制和格式校验我之前见过有人往服务器传了10MB的gif导致页面卡死这种低级问题在代码里限制一下就好。4. Vue前端实现要点与页面链路4.1 前端项目结构与路由权限控制前端这边我用的是Vue3VitePiniaElement Plus的组合整套项目从npm create vue初始化开始。目录结构按视图模块拆分views/user用户端页面宠物档案、下单页、订单列表、订单详情、个人中心views/worker服务端页面可抢订单列表、我的接单、服务记录、收入统计views/admin管理后台页面用户管理、服务人员审核、订单管理、数据统计api目录每个模块一个请求文件统一封装axios实例router目录定义路由和路由守卫。路由守卫的逻辑我写在全局前置守卫里每次跳转前检查本地是否有token没有就跳登录页有token则根据当前用户角色过滤可访问路由遇到无权访问的路由跳转到403提示页。这里要注意的是Vue的router.beforeEach里异步获取用户信息的操作会阻塞路由跳转我建议在应用启动时先调用一次获取当前用户信息接口把用户信息存到pinia里后续路由守卫直接用内存数据判断避免每次跳转都发请求。4.2 axios请求封装与后端联调细节axios请求封装这块我用拦截器统一做了几件事请求前带上token响应后统一处理业务状态码捕获HTTP异常弹出提示。响应体的设计是统一格式{ code: 200, message: success, data: {} }前端拿到code200才认为业务成功其余的code统一弹message提示。这样后端业务异常和HTTP异常分离前端处理起来逻辑清晰。HTTP状态码我建议只保留200、401、403、404、500这几种业务上的“抢单失败”“订单状态不对”等错误一律放在业务code里返回这样前端只需要在后端响应拦截器里统一处理401跳登录其他的按code的message提示用户就行。这里有个小坑要提醒axios默认的response.data是后端返回的body如果你不小心在后端返回了字符串而非JSON对象前端拿到的data直接是字符串后续data.code会报undefined。排查方法是在后端全局异常处理器里统一包装返回值确保任何情况下前端收到的都是标准JSON结构。4.3 地图选点与服务地址管理同城服务离不开地址。前端的实现方案是接入高德地图或百度地图的JavaScript API在用户下单页面嵌入一个地图组件让用户点击地图选点后自动回填经纬度和详细地址。经纬度存到数据库后后续可以做服务人员距离排序、片区划分这些进阶功能。地图组件这块我用的是vue-amap或amap/amap-jsapi-loader封装关键代码并不复杂。核心是监听地图点击事件设置标记点再调用逆地理编码接口把经纬度转成文字地址。地址保存在两个地方一个存到订单表方便下单时快照一个存到用户的常用地址簿里方便下次直接选择。如果小程序端做不了地图选点也可以退而求其次使用微信的wx.chooseLocation接口返回的经纬度格式也是兼容的。5. 本地部署与启动排坑实录5.1 环境准备JDK、Node、MySQL版本选择说完代码说部署。这套系统的本地开发环境我的推荐版本是JDK 1.8或11SpringBoot 2.7.x系列。不要贸然用SpringBoot 3.x因为SpringBoot 3是基于JDK 17的一些老版本的MyBatis Starter兼容性会出问题Node.js 16或18Vite要求Node版本不能太低我建议装18以上MySQL 5.7或8.0都可以但要注意数据库连接驱动的版本差异。MySQL 8需要引入mysql-connector-java 8.x并且连接URL要指定useSSLfalse和serverTimezoneAsia/Shanghai否则连数据库时会报时区异常Maven 3.6用IDEA开发的话内置Maven基本够用。IDE方面后端用IDEA前端用VSCode或WebStorm数据库可视化工具用Navicat或DBeaver。如果电脑配置一般IDEA建议开省电模式不然打开多个大文件会很卡。5.2 后端启动步骤与常见报错排查后端启动步骤我按实际操盘顺序列一遍用IDEA打开后端源码目录等Maven自动下载依赖这一步要保证网络稳定如果下载慢可以换成阿里云的Maven镜像修改application.yml配置文件里的数据库连接信息用户名、密码、库名改成自己本地的先执行源码里自带的db.sql脚本在MySQL里建库建表并插入初始数据找到主启动类右键Run看到Spring Boot的启动日志出现“Started”字样说明启动成功用浏览器或Postman访问http://localhost:8080/api/ping返回success就说明后端没挂。我在这套系统上遇到过两个高频报错提前给大家打完预防针。第一个是数据库连接失败报Communications link failure排查思路是MySQL是否启动→端口是否3306→账号密码是否正确→库是否存在→防火墙是否拦截。第二个是启动时端口被占用报Port 8080 was already in use解决方式是换端口或在命令行杀掉占用进程Windows用netstat -ano | findstr 8080查到PID后taskkill /F /PID。5.3 前端启动步骤与跨域处理前端启动要简单很多。进入项目目录后执行npm install安装依赖如果安装过程中报错优先检查Node版本是否满足package.json里的engines要求修改前端项目的请求环境配置文件一般是.env.development把VITE_API_BASE_URL改成http://localhost:8080/api执行npm run dev启动开发服务器默认端口5173浏览器访问http://localhost:5173看到登录页就说明前端没问题。前后端分离模式下最容易遇到的就是跨域问题。前端访问后端接口端口不同会被浏览器的同源策略拦截。我在后端做了一个全局CORS配置类允许http://localhost:5173的跨域请求并且允许携带token请求头。这里补充一点如果你后端配置了拦截器校验token那么预检请求OPTIONS是不能被拦截的否则前端会被跨域坑到怀疑人生。正确做法是CORS配置里直接放行OPTIONS请求。5.4 数据库初始化脚本与演示数据源码里的数据库脚本是分两部分组织的建表和初始化数据。建表部分包含上面提到的所有核心表初始化数据部分预置了一个管理员账号admin/admin123、两个测试宠物主人账号、一个审核通过的服务人员账号以及几条不同状态的模拟订单。演示数据特别重要。没有数据的空系统前端页面打开全是空白根本没法演示功能。我建议初始化数据至少覆盖不同状态的订单待支付1条、待接单1条、服务中1条、已完成2条这样前端每个状态的分页列表都能看到效果。另外把服务人员的评分初始化为4.8、接单量初始化为300条首页服务人员列表看起来才不寒酸。6. 我把这套源码再扩展开线上发布要考虑的进阶点本地跑通只算完成了一半。如果真要上线运营有几件事是最容易被忽略的。第一个是数据库备份。定时用mysqldump把库导出到备份目录至少每天一次。我见过太多人直到数据库误删才开始后悔那时候说什么都晚了。第二个是服务器部署方案最简单的是买一台云服务器装宝塔面板后端用Maven打包成jar包后通过脚本启动前端npm run build生成dist目录交给Nginx托管再把Nginx里配置一下反向代理把/api前缀转发到后端的8080端口。这套部署流程我大概踩过两三次坑才理顺最核心的点就是Nginx的location /api这一段不能配错否则前端请求全部404。第三个是安全加固。修改默认的MySQL密码、SpringBoot的Actuator端点不要暴露公网、服务人员上传的图片目录要禁止执行脚本类文件。如果是在云平台部署建议再加上简单防火墙配置只放行80、443和22端口。第四个是支付对接。这套源码里我留了支付接口的模拟实现方便本地测试时跳过真实支付。真要对接微信支付或支付宝还需要申请商户号、配置证书、回调验签等一堆工作。支付这块业务逻辑复杂建议单独拿一个迭代周期来做不要和核心功能混在一起上线。7. 常见问题速查表照着修省一半时间我把这段时间被问到最多的问题整理成一个速查表大家按需查看就行。现象原因解决方法npm install卡住不动默认镜像源慢设置npmmirror源npm config set registry https://registry.npmmirror.com前端请求后端跨域Nginx或CORS未配置后端加全局CORS配置Nginx用proxy_pass代理/api路径图片上传失败上传目录不存在或权限不足代码中自动创建目录或在配置文件中指定一个已有的可写目录登录后接口返回401token过期或未携带检查前端axios拦截器是否在请求头带上Authorization抢单接口并发异常缺少防重更新条件更新SQL加上status条件并使用返回值判断数据库连接失败时区或SSL问题连接URL加useSSLfalseserverTimezoneAsia/Shanghai中文乱码控制台和MySQL字符集不一致数据库连接增加characterEncodingutf8前端页面设置UTF-8Linux系统检查LANG部署后Nginx 404前端文件路径不对确认dist目录上传位置和Nginx的root配置保持一致这套项目的排错核心思路我总结一句话从前端请求发起开始一步步往后端链路排查先看浏览器Network面板请求是否发出再看后端日志报错最后定位到SQL和数据库层。不要一上来就怀疑代码大多数问题出在环境和配置层。8. 最后说点实际的这套系统的二次开发方向如果你拿到这套源码不知道下一步该往哪里改我根据实际运营经验给你几个方向参考。第一个方向是做多城市分站模式当前系统是按同城的单城市设计可以扩展一个城市字段把服务人员、订单、价格都按城市隔离这样就能复制到多个城市运营。第二个方向是加上宠物寄养或者宠物日托服务在服务类型里增加一个“寄养”选项订单流程从上门服务变成预约送养、到店寄养、接回宠物数据库层面只需要增加一个寄养门店表和寄养状态字段。第三个方向是增加营销玩法新客立减券、邀请有礼、会员卡包月服务这些都是在支付模块后面加一个优惠券系统就能实现的。我个人在实际操作中的体会是这类同城服务项目的成败三分靠技术七分靠运营规则。技术上只要保证订单状态不会乱、支付金额不会错、评价数据能沉淀下来就足够撑起早期业务了。与其前期花时间在微服务、容器化这些“听起来很高级”的东西上不如把核心业务链路跑通早点让用户用上。这套前后端分离的同城上门喂遛宠物系统正是按这个思路做的最小完整闭环希望对你搭建自己的项目能有点实实在在的帮助。