☰
SpringBoot+Vue+MyBatis+MySQL企业级宠物店管理系统实战解析
2026/10/13 7:23:59 网站建设 项目流程

这个项目标题我盯了很久,SpringBoot+Vue+MyBatis+MySQL,还加了“企业级”和“完整版”两个分量很重的定语。最近正好在给一个实体宠物店做数字化改造,接触到不少同类系统,对这个技术栈的选择和落地细节有了更具体的感受。今天就拿这套宠物店管理系统当例子,把前后端分离架构下从数据库设计到接口联调再到部署上线的完整链路,拆开揉碎了讲一遍。

1. 项目整体设计与技术选型思路

1.1 为什么是SpringBoot+Vue+MyBatis这套组合

先聊技术选型。宠物店管理系统本质是一个典型的业务管理系统:有权限控制、有数据增删改查、有订单流程、有统计报表。这类系统选技术栈,第一原则不是追新,而是稳定和团队上手快。

后端用SpringBoot,核心价值是“约定优于配置”。以前用SSH或者SSM整合,光XML配置就能写几百行,数据源、事务、扫描路径都要手动声明,新成员入职第一周基本都在看配置。SpringBoot把这些问题收敛成一个个Starter,pom里加依赖、配置文件里写参数,应用就能跑起来。这可是实打实地缩短了项目初始化周期,对于宠物店这种预算有限、节奏快的业务场景,开发效率直接决定项目能不能按时交付。

持久层选MyBatis而不是JPA/Hibernate,是因为宠物店业务里SQL相对复杂且多变。比如统计某个月份宠物销售数量、按品种分组计算货值、查询寄养时间超过7天的宠物列表,这类报表类SQL用原生SQL写特别顺手。MyBatis允许你直接控制SQL语句,xml文件和Mapper接口分离,后期优化SQL也不需要动Java代码。相对而言JPA虽然省了手写SQL的功夫,但复杂统计场景下的写法反而绕,学习成本和排错成本都更高。

前端选Vue,主要是渐进式和组件化的优势。宠物店的前台浏览页、后台管理页可以拆分成独立组件,Element UI的表格、表单、弹窗组件直接复用,一个人几天就能搭出像样的管理界面。配合Vue Router做SPA路由切换,用户体验比传统JSP刷新页面舒服得多。

数据库用MySQL,这个没什么好纠结的。MySQL在中小型应用里性价比最高,InnoDB引擎支持事务和行级锁,宠物店的订单、库存、寄养记录这些场景需要强事务保障,InnoDB是最可靠的选择。存储过程、视图、触发器这些特性也都有,日常管理系统完全够用。

1.2 单体架构足以支撑企业级场景

有人看到“企业级”三个字,第一反应是微服务、分布式、消息队列那一套。但我的建议很直接:像宠物店这种体量的系统,单体架构比微服务合理得多。

微服务解决的是团队规模大、模块独立部署、高并发弹性伸缩的问题。宠物店系统的核心用户是店内员工,高峰期也就几十人同时操作,服务端QPS可能都不到100。这时候拆微服务,等于用一群大象拉一辆小马车——注册中心、配置中心、网关、链路追踪,每个组件都是成本,都是运维负担。

这套系统用的是模块化单体的思路。代码上按业务拆包,controller、service、mapper分层清晰,未来真要拆微服务,按模块边界切分也不难。数据库保持单库,通过外键和业务逻辑维持一致性,避免了分布式事务的麻烦。Redis只用来存Token和临时缓存,并不承载核心业务数据,部署结构简单,一台云服务器就能跑完整套系统。

选型这件事,合适比高级更重要。技术是为业务服务的,宠物店老板不会因为你用了微服务就多付钱,但他会因为你系统稳定、开单快、库存不出错而愿意持续使用。

2. 核心功能模块拆解与数据库设计

2.1 宠物店业务有哪些特殊之处

宠物店管理系统和普通电商系统有不少区别,这是这类项目里最容易踩坑的地方。

第一个特殊点:商品里有“活体”。宠物本身的库存、价格、状态和普通商品完全不同。一只幼犬卖出去之后具体状态怎么记录?疫苗打了几针?驱虫做了没有?它的父亲母亲是谁?这些信息需要存储在独立的宠物档案表里,而不是硬塞进商品表。订单里买了宠物,订单明细还必须关联到具体的宠物档案记录上,而不是一个笼统的商品编号。

第二个特殊点:服务类项目多。寄养按天计费、美容按次收费、医疗按项目收费。这些服务项目没有实体库存,但有排期、有状态流转。寄养从“已预订”到“寄养中”再到“已接走”,每个状态都要记录时间节点,方便结算和追溯。

第三个特殊点:会员和宠物绑定关系很强。一个会员可能带两只狗来店消费,每只狗的品种、生日、过敏史都要记录,推荐宠物粮食时才能对症推荐。数据库设计时,客户表和宠物档案表需要单独维护,再在订单环节关联起来。

2.2 数据库表设计的关键字段和经验

数据库是整个系统的地基,表结构设计不好,后面写代码全是坑。我基于这套系统的完整版数据库脚本整理出核心表结构,字段设计上有些细节值得展开说。

用户表(sys_user):账号、密码、姓名、角色ID、手机号。密码字段设计为加盐哈希存储,角色字段和权限绑定。这里有个容易忽略的点——删除状态字段(is_deleted)比直接物理删除更安全,误删了还能恢复。

宠物信息表(pet_info):这是宠物店系统最核心的表。字段设计需要考虑:宠物名称、品种、性别、出生日期、毛色、售价、状态(在售/已售/寄养中)、疫苗记录、驱虫记录、健康状态、图片URL。宠物状态字段单独列出来,是因为它和订单、寄养功能都有关联,不能单纯依赖库存字段。

商品表(product_info):普通商品归类处理,包括宠物粮食、玩具、药品、日用品。设计时把商品类型字段(product_type)区分开:实物商品需要库存管理,服务类商品(如寄养、美容)则不需要。价格字段用decimal类型,不要用float,否则结算会出精度问题。

订单表(order_info):订单号、会员ID、订单状态、商品总金额、实付金额、优惠金额、下单时间、支付时间、完成时间、备注。订单状态我用int存储,0待支付、1已支付、2已发货、3已完成、4已取消。用int比字符串更省空间,状态流转在代码里做成枚举,可读性也不差。订单号不要用数据库自增ID,我建议使用时间戳+随机数组合生成,避免订单号泄露当日单量。

订单明细表(order_item):订单ID、商品ID或宠物ID、商品名称、商品单价、购买数量。这里有个关键点:冗余商品名称和单价。因为商品价格后期会调整,如果不冗余,历史订单展示出来的价格就是错的。这个经验是我踩坑踩出来的。

寄养记录表(boarding_record):宠物ID、会员ID、进入时间、预计离开时间、实际离开时间、寄养费用、状态。寄养费用的计算涉及跨天计费,数据库设计时预留“计费状态”字段,标记已结算和未结算,避免入住时忘记结算导致跑单。

会员表(member_info):姓名、手机号、会员等级、积分、余额、创建时间。宠物店会员体系一般比较简单,但余额字段建议单独成表或单独字段,为后续做充值赠送活动留余地。

设定表之间关系时要特别注意:一份订单对应多件商品,所以用订单主表和明细表分离;一只宠物可以多次消费,所以订单表里宠物ID作为逻辑外键关联,不一定要求数据库物理外键约束。我个人的习惯是逻辑外键为主,物理外键尽量少用,因为业务变更时物理外键的修改成本太高,ORM框架也更喜欢逻辑外键。

3. 实操过程与核心环节实现

3.1 环境准备与工程搭建

环境这块版本锁死,能避免很多莫名其妙的问题。JDK必须在1.8以上(推荐用8或11),Node版本14以上,MySQL用5.7或8.0均可。我整理过一套已验证过的版本组合,直接照用即可。

后端工程通过Spring Initializr生成初始骨架,关键依赖如下:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.20</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>

前端用Vue CLI脚手架创建,UI组件库选Element UI,路由用Vue Router,HTTP库选Axios。这里提醒一句:Vue CLI创建项目时如果遇到“sass”相关的编译报错,多半是node-sass和当前Node版本不兼容,建议直接用npm使用dart-sass替代。

3.2 后端核心配置与代码实现

后端配置集中在application.yml。有几个配置我之前反复踩坑,这里单独拎出来说:

server: port: 8080 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/pet_shop?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: yourpassword mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.petshop.entity configuration: map-underscore-to-camel-case: true

url里有两个参数务必检查:serverTimezone=Asia/Shanghai避免时间差8小时问题,allowPublicKeyRetrieval=true解决MySQL 8.0的认证问题。MyBatis的map-underscore-to-camel-case=true可以自动把数据库的下划线字段映射成驼峰属性,少写很多resultMap。

分页查询是管理系统的刚需。我习惯在MyBatis里配置PageHelper分页插件:

@Configuration public class MyBatisConfig { @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); paginationInterceptor.setOverflow(true); paginationInterceptor.setLimit(500); return paginationInterceptor; } }

这里Overflow设置成true,意思是页码超出总页数时自动回到最后一页,避免前端传一个越界页码导致全表扫描。

登录认证用JWT实现。登录成功后生成Token,拦截器校验Token并解析用户信息。JWT的三段式结构(Header、Payload、Signature)天然适合这种无状态认证场景。签发Token的代码很简单,但要注意过期时间的设置,我一般设成12小时,店员上班期间不需要反复登录。

@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (request.getMethod().equals("OPTIONS")) { return true; } String token = request.getHeader("token"); if (StringUtils.isBlank(token)) { response.setStatus(401); response.getWriter().write("未登录或登录已过期"); return false; } try { Claims claims = Jwts.parser().setSigningKey("pet-shop-secret-key") .parseClaimsJws(token).getBody(); request.setAttribute("userId", claims.get("userId")); return true; } catch (Exception e) { response.setStatus(401); response.getWriter().write("无效的令牌"); return false; } }

有个细节很多人会忽略:拦截器放行路径要配置好。登录接口、验证码接口要放行,静态资源也要放行,其余接口全部拦截。我建议在WebMvcConfig里注册拦截器时用addPathPatterns("/api/**")和excludePathPatterns("/api/login"),这样新加的接口默认被保护,不担心漏网之鱼。

3.3 前端核心页面与接口联调

前端工程我习惯按模块划分目录:views放页面,api放接口请求,router放路由配置,store放全局状态(比如用户信息)。

Axios请求封装是个必做的环节。统一处理BaseURL、Token注入、错误码拦截,避免每个页面都写一遍:

import axios from 'axios' import router from '../router' const service = axios.create({ baseURL: '/api', timeout: 15000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['token'] = token } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code === 401) { localStorage.removeItem('token') router.push('/login') } return res }, error => { Message.error(error.response?.data?.message || '请求失败') return Promise.reject(error) } ) export default service

这里前后端接口返回格式必须约定一致。我们统一用{ code: 200, data: {...}, message: 'ok' }的结构,code=200是成功,401是未登录,500是服务器异常。前端封装好之后,后续写页面只需要调用具体接口函数,不用每个地方都做错误处理,省很多事。

路由守卫控制页面访问权限,这步必须在路由里做,而不是只在按钮上做:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path === '/login') { next() } else if (!token) { next('/login') } else { next() } })

路由守卫配合后端Token校验,形成了双重保障。前端能拦掉大部分未登录请求,后端兜住漏网请求,两头堵住权限漏洞。

前台宠物展示页面的实现有一个要点:图片懒加载。宠物店图片多,如果没有懒加载,首页同时加载几十张宠物图片,页面打开速度会非常差。Element UI自带的El-Image组件支持懒加载属性,用上即可。

购物车和订单提交流程,核心逻辑在于库存锁定。用户下单时,前端把订单明细提交到后端,后端要先判断宠物状态是否为在售,商品库存是否足够。如果库存不足直接回滚,成功则扣减库存并写入订单。这里一定要用数据库事务,在Service层加上@Transactional注解,否则并发情况下出现超卖会是严重的线上事故。

4. 常见问题与排查技巧实录

4.1 高频问题速查表

整理了一套我实际跑这套系统时遇到的高频问题,已经按现象、原因、解决方案三种维度整理出来了。

现象可能原因解决方案
启动报数据库连接失败MySQL未启动或账号密码错误检查MySQL服务状态,核对url和账号密码
中文乱码数据库字符集不是utf8mb4建库时指定utf8mb4,JDBC url加characterEncoding=utf8
MyBatis绑定异常Mapper接口和XML文件namespace不匹配检查Mapper接口全限定名和XML的namespace完全一致
接口返回时间差8小时JDBC连接串没设时区url加serverTimezone=Asia/Shanghai
前端请求404跨域问题或路由路径不对检查前后端端口是否代理,Vue的baseURL是否匹配
刷新页面404前端history路由部署问题改用hash模式,或服务端配置history回退
上传图片失败静态资源映射没配置配置资源映射目录,或改用OSS存储
Token失效频繁JWT过期时间太短延长过期时间或实现RefreshToken机制

4.2 部署上线前容易踩的隐蔽坑

暴露型问题和启动报错比较容易被发现,另一类“不报错但结果不对”的隐蔽问题,才最容易让大家在开发阶段栽跟头。

第一个坑是金额精度。前端传过来的金额如果是浮点数,decimal类型在MySQL里比较时容易出精度问题。我的做法是前端所有金额相关的数据都乘以100存储为分(整数),后端返回时再除以100转为元。虽然响应报文里看到的是整数,需要自己转换,但这种做法彻底杜绝了浮点误差,尤其在订单结算和余额扣减的场景下非常稳妥。

第二个坑是逻辑删除字段不统一。有的表用is_deleted,有的表用status=-1,有的表甚至没做逻辑删除。我建议全项目统一约定:所有业务表必须有is_deleted字段,0正常,1已删除。查询时统一使用WHERE is_deleted=0,这样后端写SQL时,条件规范统一,不会出现漏网数据。

第三个坑是图片存储路径问题。本地存储的图片路径在测试环境可能正常,一旦换服务器或者迁移,绝对路径就会失效。我的建议是上传图片时只保存相对路径(如/upload/2024/xx.jpg),前端显示时拼接服务器地址或域名。这样换环境部署只需要改一个前端环境变量,不用改数据库。

4.3 开发到上线的完整流程清单

这套系统从开发到上线,我总结出一份固定的流程清单,按这个顺序走可以少踩很多坑:

  1. 数据库初始化阶段:建库、建表、跑初始化脚本。此阶段需要确认字符集是utf8mb4,排序规则正确,存储过程等一次性对象验证无误。
  2. 后端骨架搭建:SpringBoot工程创建、依赖引入、配置文件编写、MyBatis基础Mapper测试通过。
  3. 前端骨架搭建:Vue CLI创建工程、Element UI引入、路由配置、Axios封装、跨域代理配置。
  4. 核心功能开发:按“登录认证→宠物管理→商品管理→订单流程”的优先级依次实现,每个模块先完成后端再连前端。
  5. 权限和拦截器验证:未登录访问接口必须返回401,登录后Token过期必须提示重新登录。
  6. 本地联调测试:前端起8081端口,后端8080端口,通过Vue脚手架代理请求后端接口,检查是否还有跨域报错。
  7. 生产环境构建:前端npm run build生成dist目录,后端mvn package打成jar包,部署到云服务器。
  8. Nginx反向代理配置:把前端静态文件交给Nginx,接口请求反代到后端端口,这样前后端在同一个域名下,不会产生跨域问题。
  9. 回归测试清单:登录、宠物上下架、下单操作、寄养结算、报表查看这五个核心链路必须完整走一遍。
  10. 备份策略:数据库定时备份脚本上线,每天凌晨全量备份,保留最近7天备份文件。

这套清单每次部署新项目我都会直接复用,按步骤走虽然费点时间,但比出了问题再排查要高效得多。

5. 项目后续还能怎么扩展

这套系统跑起来之后,它解决的问题已经覆盖了宠物店日常经营的核心链路。但如果你想让系统发挥更大的价值,有几个扩展方向非常值得考虑。

第一个方向是预约功能。宠物美容、宠物洗护这类服务项目,预约看板是很实用的功能。日历式看板可以直观展示每天各个时间段的预约情况,这需要在现有基础上增加预约表,并且和排班表结合起来。

第二个方向是移动端适配。很多宠物店老板希望店员用手机就能查库存、开票、办理寄养手续,而不是每次都去前台电脑操作。现有Vue后台可以引入移动端框架或者开发一套小程序端,服务端接口可以原样复用,只需要增加移动端适配的接口版本。

第三个方向是进销存报表整合。现在系统有了订单、库存、寄养记录,但进销存报表大多是简单查询。如果能增加按日销售额、毛利计算、品种销售排行、库存预警值设定这些功能,就能服务于老板的选品和营销策略,价值比单纯抓管理大得多,这也是很多宠物店经营者真正关心的东西。

我从这套企业级宠物店系统中体会最深的一点是:管理系统类项目,技术本身不会成为瓶颈,业务理解才是真正拉开差距的地方。你只有理解了宠物店“活体+服务+商品”三重业态混合的特殊性,才能设计出贴合实际场景的数据库和业务逻辑。把这套底子打牢,换一个行业场景,做超市管理系统还是做美容院管理系统,内核也都是互通的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询