ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue打造流浪宠物管理系统:从需求到部署全解析

SpringBoot+Vue打造流浪宠物管理系统:从需求到部署全解析 敲下这行字的时候距离我完成这套流浪宠物管理系统已经过去一年多。当时接下这个需求起因是一个社区救助站的朋友跟我抱怨手里的猫狗档案全靠Excel登记领养人信息、疫苗日期、回访记录混在几张表里真要去查某只猫的来龙去脉特别费劲。于是我用SpringBootVue做前后端后端语言完全走Java体系持久层接MySQL数据库和MyBatis花了大约三周时间给他搭了一套web管理系统。这套系统最核心的价值就是把“宠物登记-领养申请-审核-回访”这条主流程串起来了同时附带公告发布、意见反馈等辅助功能。今天这篇博文我把从需求拆解、数据库设计、后端接口、前端联调到最终部署的完整过程重新梳理一遍既适合正在做Java毕业设计或者想练习前后端分离项目的读者也适合想看看一套小型管理系统到底该怎么落地的朋友。1. 项目整体设计与技术选型思考1.1 核心需求解析一个流浪宠物管理系统到底管什么我先没急着打开IDE敲代码而是花了两天时间在救助站蹲点看他们每天到底在忙什么。救助站一天的高频操作大概是接收一只新流浪猫狗给它拍照取样记录救助地点和身体状态有人来申请领养需要填写个人信息和居住情况管理员得判断这个家庭合不合适领养出去之后还需要回访确认宠物在新家的生活状态。把这些场景抽象成系统功能就是宠物档案管理、领养申请与审核、领养后回访、用户角色权限。再往下拆公告栏和留言反馈这类功能是让这个系统更像一个信息平台而不是一张随手的记录表。这里有个很重要的思路项目刚开始时一定要控制功能边界。流浪宠物管理如果往大了做可以接志愿者排班、物资库存、财务捐赠统计、合作医院对接但这些业务本身在真实救助站里就是动态变化的需求一直加项目很容易烂尾。我把最小可用闭环定为“宠物入库→展示→申请领养→审核→出库→回访”其它功能在源码里只做基础版本。这样做的好处是主线清晰代码结构不会散技术难点也集中在宠物列表动态查询和领养审核事务上非常适合用来深入学习SpringBootMyBatis的配合方式。1.2 技术栈选型背后的逻辑为什么偏偏是SpringBootVueMySQLMyBatis这套技术组合放在现在来看不算最新潮但胜在非常稳。SpringBoot把SSM时代大量手动维护的XML配置收编成了自动装配内嵌了Tomcat想启动项目直接一个main方法就能跑这对我这种经常要给别人演示项目的人太友好了。前端用Vue是因为它组件化和数据驱动的写法比老一套的jQuery拼接DOM省太多心宠物列表、领养表单、审核弹窗这种交互密集的页面用Vue写起来思路非常顺畅。MySQL免费、轻量、资料多一个几万条数据的救助站管理系统完全够用没必要为了“显得高级”去上重型数据库。MyBatis保留了SQL裸写的自由度像宠物列表这种需要按关键词、品种、状态动态拼接查询条件的场景用它的动态SQL写起来十分顺手。版本选择是个容易被人忽视的坑。我当时的SpringBoot用的是2.7.2而不是3.x原因是3.0起有一部分老组件不兼容国内大量教程和问题解决方案也还停留在2.x生态真出问题你搜到的答案会更对症。JDK用8就够了Maven用6.3.xMySQL用5.7或者8.0都可以连接串上注意一点时区和ssl问题后面我会专门讲。前端我用的是Vue2.6搭配Element UI这套组合在管理后台类项目里非常成熟如果你已经装了Vue3环境想把组件库换成Element Plus也完全可行路由和组件写法只要跟着Vue3语法调整即可并不影响后端接口设计。理解这套技术栈的组合动机比单纯把代码跑起来更有价值因为你以后换SSM、换MyBatis Plus甚至换Spring Boot 3核心思路都是一样的。2. 数据库设计与后端核心实现2.1 数据表结构设计从业务到字段的映射表结构的好坏几乎决定后面接口写起来是否顺手。我在动手写后端之前先和救助站确认了每个页面要展示的数据项再逆推出需要哪些字段。比如宠物档案页面要展示救助地点和疫苗日期那么pets表就必须有rescue_time和vaccine_time领养审核页要能说明拒绝原因所以adopt_application表必须有audit_remark字段。表格列出来了代码里基本就不会缺胳膊少腿。pets表设计如下字段名类型说明idbigint主键pet_novarchar宠物编号格式如ST-2024-001namevarchar宠物昵称typetinyint1猫 2狗 3其它breedvarchar品种colorvarchar毛色sextinyint1公 2母agevarchar年龄用“2个月”“1岁半”这种显示health_statusvarchar健康状况描述is_neuteredtinyint是否绝育vaccine_timedate最近疫苗时间deworm_timedate最近驱虫时间rescue_timedate救助时间rescue_addressvarchar救助地点character_notevarchar性格描述statustinyint1待领养 2已领养 3暂养中 4已寻回 5离世cover_imagevarchar封面图URLimagestext多图URL逗号分隔create_bybigint录入人create_timedatetime创建时间update_timedatetime更新时间del_flagtinyint逻辑删除标记为什么这样设计类型和状态这类字段我用tinyint而不用varchar是因为后面做条件筛选和统计时数字查询更高效前端展示时再通过字典翻译成文字。pet_no手工生成一个易于识别的编号纸质档案和线上系统能对得上这在救助站实际使用中特别有用。images字段存逗号分隔的URL虽然不符合极高的范式要求但胜在简单前端拿到后split一下就能展示多图小项目里完全够用没必要为这点灵活性引入一张宠物图片子表。del_flag是每个表都要有的逻辑删除标记防止把重要档案物理删除后无法恢复所有查询都默认加del_flag0。领养申请表adopt_application的核心字段是这样设计的字段名类型说明idbigint主键pet_idbigint关联宠物user_idbigint申请人用户IDapplicant_namevarchar申请人姓名phonevarchar联系电话addressvarchar居住地址house_typevarchar自有住房/租房family_consentvarchar家庭是否一致同意reasontext领养理由statustinyint0待审核 1通过 2拒绝audit_user_idbigint审核人audit_timedatetime审核时间audit_remarkvarchar审核备注create_timedatetime申请时间事务方面领养审核通过需要同时改动adopt_application.status和pets.status这两个动作绑定在一个事务里要么都成功要么都回滚。在SpringBoot里就是在Service方法上加Transactional如果你只改了申请表忘了同步宠物状态就会出现“宠物还挂在待领养列表上但申请单已经是通过状态”这种数据一致性问题。这一点在面试中也常被问到能讲清楚为什么需要事务比背概念强得多。2.2 SpringBootMyBatis接口设计从Controller到Mapper的一条完整链路后端包结构我划分为controller、service、mapper、entity、dto、common这几个包。common里放统一返回类Result、业务异常类、JWT工具、全局异常处理器。接口统一返回Result格式结构是{code, message, data}前端axios拦截器里只需要对code做判断代码很清爽。以宠物分页列表为例前端传参是pageNum、pageSize、name、type、statusController写得很薄。下面是一段核心代码节选RestController RequestMapping(/api/pet) public class PetController { Autowired private PetService petService; GetMapping(/list) public ResultPageResultPetVO list(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, String name, Integer type, Integer status) { return Result.success(petService.page(pageNum, pageSize, name, type, status)); } }Service层内部去组装查询条件和分页Mapper层用XML写动态SQL这是MyBatis最舒服的部分。比如当用户没选状态时status条件就自动忽略用户输入了名称关键词则使用like模糊匹配。我在XML中会这样写select idpageList resultTypecom.example.pet.entity.Pet select * from pets where del_flag 0 if testname ! null and name ! and name like concat(%, #{name}, %) /if if testtype ! null and type #{type} /if if teststatus ! null and status #{status} /if /where order by create_time desc /select分页这块我用的是PageHelper插件在Service里调用PageHelper.startPage(pageNum, pageSize)后紧接着执行Mapper方法它会自动拦截SQL追加limit再把结果包装成PageResult返回。如果你不想用插件完全可以用手写limit实现逻辑也不复杂但PageHelper能省很多样板代码。还有一个容易被忽略的点XML文件必须被Maven打包进classes目录。如果你是按标准Maven工程结构来的XML放在src/main/resources/mapper下面没问题如果放在java目录下需要在pom的resource标签里额外声明。开发时遇到Mapper方法报Invalid bound statement (not found)十次里有八次是XML没被扫描到或者namespace写错而不是SQL写错了。领养审核的接口实现是这个系统里最体现事务场景的地方。后端接收申请单ID和审核结果先把adopt_application的状态改成通过或拒绝如果通过还要把对应宠物status改成2同时记录审核人和审核时间。这里我用了Transactional一个方法里完成两个表的更新任何一步出现异常都整体回滚保证数据不会出现半截状态。3. Vue前端与联调实战3.1 前端工程初始化与路由权限设计前端我用Vue Cli创建工程现在来看Vite会更轻更快但Vue Cli的资料太多装好之后基本都是能用状态。项目里安装了Element UI、axios、vue-router、vuex。开发时npm run serve起的默认端口是8080而后端也是8080所以我把前端devServer端口改成8081并在vue.config.js里加了一个代理配置module.exports { devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: {} } } } }这样前端页面里发起/api/pet/list请求时开发环境会由代理转发到后端的8080端口不需要在后端处理跨域。axios封装方面我在请求拦截器里带上token响应拦截器里统一处理code非0的错误比如登录过期跳回登录页。路由权限是这类管理系统的常规需求。用户登录成功后后端返回用户信息和角色类型前端根据角色动态生成菜单。比如管理员能看到系统管理和领养审核菜单普通用户只能看到宠物展示和我的申请。我在router.beforeEach拦截器里判断本地是否存有token如果没有token就直接跳转到登录页如果有token但还想再进登录页就重定向到首页。动态路由用的是router.addRoutes把当前角色拥有的路由表追加进去这样刷新调用beforeEach时会重新计算路由不会出现所谓的空白页问题。3.2 核心页面实现细节与前后端联调宠物列表页是前台最核心的页面。上面是搜索区包含品种下拉、状态下拉和关键词输入框中间是宠物卡片区每张卡片显示封面图、昵称、品种、健康状态点击详情能打开弹窗看多图和性格描述。这个布局用Element UI的el-card加el-dialog可以很快搭出来图片预览用el-image组件能直接放大查看细节比写一堆lightbox省事。领养申请流程是整个前端最需要注意的一环。用户点击申请领养后弹窗表单里除了基础的姓名电话还要求填写住房情况、家庭成员是否一致同意、领养理由等信息。这些字段和后端adopt_application表完全对齐。提交成功后前台这个宠物卡片最好即时把状态从待领养改成审核中避免同一只宠物被多人同时申请这个效果可以在提交成功后直接更新本地数据。管理员端审核页用el-table展示待审核列表操作列里放通过和拒绝按钮。点击通过时前端需要确认后端返回的code成功后刷新列表并提示如果后端返回的是BizException全局异常处理器会把code置为500并返回错误信息前端直接用ElMessage弹出即可。联调时最常见的现象是接口通了但页面数据不对这种问题一半是后端数据结构跟上一版接口对不上另一半是前端字段名跟后端实体字段名拼写不一致。我在实际开发中用了一个笨但有效的方法先打开浏览器F12看Network里接口返回的原始JSON再对照页面调试不要一边猜一边写代码。图片上传这部分在联调里坑不少。Element UI的el-upload组件可以把action直接指向后端/api/upload但需要动态传header里的token后端接收MultipartFile之后把文件写到配置好的本地目录并返回一个可访问的URL。这里有个容易踩的雷图片文件和SpringBoot启动目录如果不在同一个路径下前端直接访问http://localhost:8080/upload/xxx.jpg可能404。解决办法是写一个WebMvcConfigurer把本地磁盘目录映射成/upload/**静态资源路径这样打包部署后也能正常访问。4. 部署、避坑与优化记录4.1 数据库初始化与本地环境准备有些朋友会在拿到源码后第一步就卡住其实多数问题出在数据库没初始化好。我习惯把建库建表的脚本整理成一个init.sql表顺序按依赖关系排列先建users、pets再建adopt_application、adopt_follow避免外键关联出现找不到表的错误。如果你用Navicat直接新建一个pet_shelter库然后把整个SQL文件拖进去运行就行如果用命令行执行source /path/init.sql也可以。MySQL版本选择上本地开发用8.0体验更新JDBC连接串需要留意两个参数useSSLfalse和allowPublicKeyRetrievaltrue。否则启动时容易遇到SSL连接异常或者Public Key Retrieval is not allowed这样的报错。如果是要部署到一台配置不高的老服务器上MySQL 5.7反而更稳妥5.7.44是这些年比较常见的稳定版本内存占用比8.0低配合2G内存的小云主机完全跑得动。另外我推荐把数据库连接参数放到application.yml中并配置一个后缀prod的profile方便切换本机和服务器环境。连接串里设serverTimezoneAsia/Shanghai可以避免数据库时间和本地时间差8个小时的问题。这些配置细节看起来琐碎但很多项目卡在启动阶段都是因为连接串少一个参数。4.2 前端打包放进SpringBoot还是单独部署Nginx这是我在演示这个项目时最常被问到的问题。两种做法都可用决定标准是看这个系统要给谁用、部署环境多简陋。方式一是最省事的“单包模式”前端执行npm run build后把dist目录里的静态资源全部复制到SpringBoot项目src/main/resources/static下然后正常mvn package打成一个jar包用java -jar启动后访问 http://localhost:8080/index.html 就能看到完整系统。这种方式适合演示、答辩、个人学习只有一个进程部署成本几乎为零。需要注意前端路由必须用hash模式也就是URL里带#否则用户直接刷新某个地址会404因为后端没有为history路由配置专门的转发规则。方式二是“前后端分离部署”前端dist放到Nginx的html目录后端jar单独跑在8080端口Nginx配置把/api开头的请求反向代理到jar。这种结构更接近真实生产后面要扩容前端或后端都方便也能用Nginx做静态资源缓存和同一服务器上多站点共存。如果你打算让办公室内网的同事同时使用方式二更合适。我把两种方式用一个表格对比一下对比项单包模式前后端分离Nginx部署难度极低一个jar中等需要配Nginx前端路由建议hash模式可用history需配置try_files静态资源服务SpringBoot默认静态目录Nginx性能更好适合场景毕设演示、内网小范围使用正式项目、需要单独运维前端维护成本低但前端改动要重新打包jar稍微复杂但边界清晰4.3 高频问题与排查技巧实录写项目时我遇到过很多奇奇怪怪的报错挑几个最常见的列出来给还没踩过坑的朋友提个醒。第一个是Invalid bound statement (not found)。这条报错出现在调任意Mapper方法的时候。发生原因主要是MyBatis的mapper-locations没配置或者XML的namespace与实际接口不一致或方法id没对齐。检查顺序是看application.yml里有没有mybatis.mapper-locations: classpath:mapper/*.xml再打开XML看namespace是否等于接口全限定名最后确认id与Mapper接口方法名完全一致。这三处都对了问题基本就消失。第二个是数据库乱码。中文插入后变成???通常是数据库连接串没写characterEncodingutf8以及表默认字符集不是utf8mb4。在建库时把默认字符集设成utf8mb4连接串加上characterEncodingutf8再从Navicat里重建一次表基本能解决。第三个是跨域CORS block。如果你没有用前端代理而是直接在前端域名或端口去请求后端接口浏览器会拦截。最简单的是在后端加一个全局CorsFilter允许指定来源、方法、请求头。但如果开发环境配置了devServer.proxy就不需要额外加CORS避免同一套接口既走代理又放跨域反而更容易出现选项预请求混乱。第四个是时间格式不对。返回给前端的日期字段有时会变成时间戳有时会慢8小时。统一做法是在application.yml里配置spring.jackson.date-formatyyyy-MM-dd HH:mm:ss和time-zoneGMT8实体字段类型用LocalDateTime这样接口返回的是一段可读字符串前端不用再额外格式化。第五个是图片上传后访问404。如前文所说文件写到本地磁盘后要配置静态资源映射。如果你用Spring Boot 2.x实现WebMvcConfigurer的addResourceHandlers把本地目录映射为/upload/**如果你用更高级的Nginx方式部署也可以交给Nginx直接代理图片目录后端只保存相对路径。第六个是和MyBatis缓存有关的疑惑。有次我发现修改了宠物状态后下次查询还是旧数据第一反应以为是一级缓存的问题排查后发现其实是自己写了个二级缓存机制数据更新后没有清缓存造成的。MyBatis一级缓存默认作用在SqlSession内单体应用里一次请求通常对应一个SqlSession所以同一次请求内重复查询会走缓存二级缓存默认关闭除非你显式配置。在小型管理系统里我建议先用好数据库索引别急着上二级缓存把缓存问题留到需要压测时再处理反而更稳。在排查这些问题时我的习惯是一层层拆先看网络请求有没有发出去再看后端日志走到哪个方法最后看SQL打印有没有问题。SpringBoot里把mybatis日志级别调到debug让控制台打印出每次的preparing语句和parameters能省非常多时间。5. 项目走通之后还能往哪些方向扩展5.1 功能层值得做的增量这套系统在功能上还有一些地方可以继续加深。比如宠物列表和公告页如果访问量变大可以用Redis做一层热点数据缓存把半小时内的热门查询结果存起来减少数据库压力。领养审核通过后如果希望通知申请人可以接入邮件或短信模板或者做成一个小程序消息推送救助站成员运营起来更方便。再比如救助站每月要上报领养台账现在靠人工在后台点导出。我后续如果重构会引入EasyExcel把领养记录、回访列表导出成Excel管理员选个日期范围就能拿到一张规整表格这个是实际使用中最省人力的功能。喜欢做可视化的朋友还可以基于现有数据用ECharts做领养趋势折线图、宠物类型占比饼图、每月救助数量柱状图这些数据都在这几张表里写几个聚合SQL就能出图很适合当毕业论文里的系统测试和效果展示材料。如果你想把系统做成真正的平台建议增加消息中心和操作日志。现在的权限控制只做了角色判断没有把每个用户的敏感操作记录下来正式上线后一旦数据被误改没有回溯依据。操作日志表可以记录谁在什么时间改了什么字段这个功能对志愿者参与运营的公益场景很关键。5.2 一份来自实操的参考建议最后分享一点我个人的实操体会。如果你赶时间不要先纠结权限是不是做成了RBAC模型、前端要不要用TypeScript而是先把“登录-录入宠物-申请领养-审核-回访”这条最核心链路跑通。我当初前三天都在搭框架、写建表SQL、折腾环境真正跑通第一版核心功能也只花了一周左右剩下时间都花在打磨前端交互和排查各种环境问题上了。很多刚接触SpringBoot的人会觉得项目难其实难的不是编码而是不知道功能边界、表结构怎么拆、遇到报错怎么定位。把这套源码的目录结构、表结构和接口调用关系弄清楚再照着写一遍效果比看十遍视频教程要好得多。等这条主链路彻底理解了你再回头看权限、缓存、Excel导出都是水到渠成的事。
返回列表