基于SpringBoot+Vue的宠物关爱系统开发实战:从数据模型到Docker部署

发布时间:2026/9/26 16:55:29
基于SpringBoot+Vue的宠物关爱系统开发实战:从数据模型到Docker部署 做宠物关爱系统这类项目我一直有个观点业务不复杂真正考验人的是工程规范程度。SpringBoot负责后端接口Vue负责前端交互这套前后端分离的组合在Java全栈项目里太常见了但能把登录鉴权、异常处理、跨域联调、容器化部署这一整条链路都跑通就算把骨架搭明白了。这篇文章不打算停留在概念层面我会直接按实际开发顺序把数据模型、后端接口、前端页面、联调排错、部署上线这些环节逐个拆开讲适合正在做课程设计、毕业设计或者刚接触前后端分离开发想找一份完整参考的读者。1. 项目整体规划宠物关爱系统到底在做什么1.1 系统核心模块划分宠物关爱系统的用户画像很清晰家里养了猫狗、需要记录宠物健康信息、会忘记打疫苗时间、想给宠物做成长记录的宠物主人。围绕这个画像系统功能不需要做得花哨但一定要覆盖真实的使用场景。我一般把功能拆成四个核心模块宠物档案管理维护宠物基本信息包括昵称、品种、生日、性别、是否绝育、毛色、芯片编号等。这个模块是整张数据表的根其它业务都围绕宠物档案展开。健康记录管理记录体重变化、体温、就诊历史、疫苗接种、驱虫情况。其中疫苗和驱虫记录因为带有时间属性价值最高可以直接关联提醒功能。提醒与待办根据疫苗记录和驱虫记录自动生成下次到期时间在到期前几天通过系统通知提醒用户。成长相册上传宠物日常照片按时间线展示成长过程。这个模块用到的文件上传和静态资源映射也是后端开发中非常典型的场景。整套逻辑捋下来你会发现所有功能都围绕“宠物档案”这个根实体展开。所以设计时千万不要把表结构画得乱七八糟核心思路是一个主账号可以养多只宠物一只宠物拥有多条健康记录、多条提醒记录、多张照片。这就是典型的一对多关系明确了这一点后面建库建表都顺理成章。1.2 数据模型设计的几个关键取舍宠物关爱系统的数据表不需要太多我建议控制在六张以内避免过度设计表名核心字段用途userid, username, password, nickname, email用户登录注册pet_profileid, user_id, name, breed, birthday, gender, neutered宠物档案health_recordid, pet_id, type, record_date, weight, temperature, description健康记录vaccine_recordid, pet_id, vaccine_name, vaccinated_date, due_date疫苗与驱虫记录reminderid, pet_id, remind_type, target_date, status提醒事项growth_albumid, pet_id, photo_url, note, create_time成长相册设计表的时候有几点值得单独说说。主键我统一用自增id不用UUID。自增id在MySQL里走聚集索引写入性能和查询性能都更好UUID作为主键会导致索引页分裂严重数据量大时能明显感觉到插入和查询变慢。对宠物关爱系统这种数据量级来说自增id完全够用还能省掉很多麻烦。关于外键我强烈建议不加物理外键。外键约束会让每次插入和删除都去检查关联表两个表动起来性能明显下降。在SpringBoot服务端用逻辑保证关联完整性就够了——比如删除宠物档案之前先按pet_id把健康记录和相册记录删掉。这种做法在真实项目里几乎是标配别被教科书里“必须加外键”的说法困住。时间字段统一用datetime或者timestamp都行但一定要默认值方案统一。比如create_time用DEFAULT CURRENT_TIMESTAMPupdate_time用DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP。少在代码里手动set时间数据库层面自动维护省心得多。2. SpringBoot后端落地从工程骨架到安全加固2.1 工程初始化与版本选型后端我用SpringBoot 2.7.x系列。版本选择背后的逻辑很简单2.7.x是目前兼容性最稳的阶段版本无论配JDK 8还是JDK 11都能跑依赖生态也完整网上遇到的问题绝大多数都有现成答案。SpringBoot 3.x虽然已经发布很久但要求JDK 17起步部分公司内部框架和旧Oracle驱动可能存在兼容问题如果在做毕业设计反而容易踩坑。工程初始化方式两种从start.spring.io生成或者IDEA内置的Spring Initializr。我习惯直接在IDEA里建勾选Spring Web、MySQL Driver、MyBatis、Lombok这几个起步依赖。构建工具选Maven虽然Gradle也很优秀但Maven在国内环境下载依赖更稳定团队协作上手也快。YAML配置里有两个小坑提前说。第一数据库连接串后面一定要加useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8否则连接MySQL 8.x时会因为SSL握手失败或者时区问题直接报错。第二MyBatis的map-underscore-to-camel-case这行配置一定要开数据库下划线字段才能自动映射到java驼峰属性。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/pet_care?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 10MB mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true2.2 分层架构与统一返回结构后端分层我固定用Controller - Service - Mapper三层。Controller只负责接收参数和组装响应Service处理业务逻辑Mapper跟数据库打交道。很多人刚写SpringBoot控制不住把SQL拼接、业务判断全堆在Controller里刚开始数据量小不觉得等加了提醒逻辑和权限校验就乱了。分层这个事前期多花十分钟后期能省好几个小时。为了前后端协作顺畅我习惯写一个统一的返回结构Result。不管接口成功失败响应格式永远是这样public class ResultT { private Integer code; private String message; private T data; }code约定200表示成功400表示参数错误401表示未登录500表示服务端异常。前端axios拦截器拿到code之后做统一处理不需要每个页面都写if判断非常省事。这里有个经验data不要搞成泛型嵌套直接用Object接住页面再强转否则前端拿到的数据格式容易混乱。Service层的实现类还有个容易忽略的细节。注册接口需要判断用户名是否存在、密码加密存储、捕获重复异常删除宠物档案时需要连带清理健康记录和相册。这些业务校验和级联操作都应该放在Service层事务方法里用Transactional注解保证原子性。否则半路抛异常数据就脏了。2.3 登录鉴权与全局安全处理宠物关爱系统涉及隐私数据登录鉴权不能省。我用的方案是JWT无状态鉴权登录成功后生成token返回前端前端后续请求在Header里带上token后端通过拦截器校验。生成token我用jjwt这个库版本注意不要踩坑dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version scoperuntime/scope /dependencyJWT生成的时候payload部分只放userId和username别把密码、手机号这类敏感信息塞进去。JWT本身只是签名不是加密payload里放什么客户端都能看到。token过期时间我给的是24小时如果不想让用户频繁登录就把时间放宽一些。另外密码存储不要用MD5MD5已经能通过彩虹表秒级碰撞。要用BCryptPasswordEncoderspring-security-crypto包里就有每次加密自动携带随机盐就算两个用户密码一样密文也不同安全性好很多。全局安全处理还包括异常拦截和XSS过滤。用RestControllerAdviceExceptionHandler做统一异常处理把校验异常、业务异常、未知异常分别映射到对应的Result返回给前端避免堆栈信息直接暴露在接口响应里。热搜词里有关于SpringBoot项目全局过滤器处理上传PDF文件时XSS攻击的内容这个确实值得注意。实现思路是写一个Filter对GET请求的query参数和POST请求的body内容进行XSS字符清理。因为SpringMVC有RequestBody时会自动做参数绑定的问题所以不能简单只挡前端后端必须把script、javascript:这些危险片段过滤掉。我用的方案是封装一个XssRequestWrapper重写getParameter和getInputStream然后通过过滤器替换默认请求对象。Component public class XssFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { XssRequestWrapper xssRequest new XssRequestWrapper((HttpServletRequest) request); chain.doFilter(xssRequest, response); } }这个文件上传场景还有一个配套问题上传目录不要放在项目内部否则重启后文件就丢了。我会在配置里定义file.upload-dir上传时写到磁盘绝对路径再用一个ResourceHandler配置静态资源映射图片就能直接通过URL访问。3. Vue前端实现工程初始化、请求封装与核心页面3.1 前端工程初始化与环境配置前端用Vue 2 Vue CLI还是Vue 3 Vite我推荐后者这是当前的主流方向。Vite启动速度比Webpack快一个量级项目大了之后优势更明显。Node版本建议16低于14会直接报错。安装依赖的时候注意npm默认源在国外网络不好会卡在node-sass或者electron这类二进制依赖上建议直接配置淘宝镜像npm config set registry https://registry.npmmirror.com用Vite创建Vue 3项目npm create vitelatest pet-care-web -- --template vue cd pet-care-web npm install npm install vue-router4 axios element-plus echartsUI框架选Element Plus表单组件和表格组件都很完善宠物档案录入、健康记录展示这两块核心页面能省大量开发时间。如果嫌中文文档不全直接看官方组件源码也行质量还不错。开发环境有个关键配置vite.config.js里加开发服务器代理。前端在8081端口跑接口在8080端口直接用axios请求必跨域。代理配置如下export default defineConfig({ plugins: [vue()], server: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } } } })这个代理方案比后端开CORS干净得多前端请求的时候直接写/api/pet/list浏览器里根本感知不到跨域存在。后面部署到Nginx再在Nginx层做同样的代理规则代码不用改。3.2 请求封装与路由权限axios封装是前端工程的标配。我会单独建一个src/utils/request.js统一配置baseURL、超时时间、请求拦截器和响应拦截器。请求拦截器里的核心逻辑是自动附带tokenrequest.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer token } return config })响应拦截器做三件事服务端返回200时直接解包、返回401时清空登录状态并跳转登录页、其它错误统一用Element Plus的message组件提示。路由权限这里有个容易忽略的点Vue Router的全局前置守卫一定要写好。我们前端把路由拆成公开路由登录页、注册页和私有路由宠物列表、健康记录、相册在守卫里判断router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path ! /login !token) { next(/login) } else { next() } })很多系统页面能打开但接口一直401多半是前端守卫没拦住未登录请求导致页面一片空白再被后端弹回来。把这个守卫加上再做一次路由拦截体验明显不一样。3.3 核心页面实现细节宠物档案列表页用Element Plus的表格组件加搜索条件这里有两个实用的点。第一是搜索和分页联动。搜索表单提交时搜索条件字段要跟分页字段一起传给后端。后端PageHelper接收pageNum和pageSize搜索条件放到实体对象里。注意PageHelper的页码从1开始前端传的pageNum初始值就应该是1。第二是图片处理。宠物头像上传用的是Element Plus里el-upload组件注意设置action地址同时带token。上传成功后组件会返回文件地址把地址直接塞进表单即可不需要走文件转base64那套。健康记录页我加了ECharts折线图一年内的体重变化一目了然。初始化图表在mounted里发起接口请求拿到数据后再setOption。一个容易踩的坑是图表容器在固定宽高的父容器里渲染不出来得给容器设置一下尺寸ECharts初始化前如果getComputedStyle(el).width是0图表就会渲染不完整。Vue 3组合式API的管理我也提一下路由守卫写在src/router/index.js全局状态用Pinia宠物信息、登录用户信息这些跨页面使用的数据放store里一次接口请求全站缓存使用。不推荐每次进入页面都发请求拉宠物列表用户体验差后端压力也大。4. 前后端联调、跨域处理与常见问题速查4.1 联调环境配置与跨域解决前后端分离开发联调阶段最常见的三个问题分别是跨域、接口路径对不上、参数格式不一致。跨域问题上面已经给了代理方案但还有一类情况要注意如果后端服务在局域网另一台机器上比如后端同学电脑前端代理的target要对应改IP不能一直写localhost。上线部署后用Nginx反向代理同样需要配置一组location /api规则转发到后端实例。这里我建议前端开发时baseURL统一以/api开头这样代理到后端直接rewrite掉前缀两边都省事。接口路径对不上的问题根源在于前后端没有对齐契约。我的习惯是要求后端先出一份接口文档Apifox或者Swagger都行。前端按文档用Mock数据把页面写出来后端按文档实现真实逻辑联调阶段以文档为准修改细节而不是两个人坐在一起对着浏览器猜字段名。字段名规范也要提前约定后端返回下划线字段还是驼峰字段统一一种。参数格式不一致也是个经典坑。比如前端传了一个空字符串后端用RequestParam Integer接收SpringMVC类型转换时直接抛400。处理办法是后端对判空操作统一让前端约定不传字段时省略key而不是传空串。4.2 常见问题与排错思路把我在这个项目上实际踩过的坑整理成一张速查表应该能帮你少走不少弯路现象可能原因处理方式前端请求报401token过期、Authorization头没带检查请求拦截器代码检查后端filter绕过配置后端接口报404前端代理路径没匹配确认vite代理的rewrite规则后端context-path配置中文乱码数据库连接串没加utf8、页面meta缺失连接串加characterEncodingutf8重启应用上传图片访问403静态资源映射没配置ResourceHandler配置磁盘路径映射到/upload/**数据库连接超时MySQL空闲连接被回收连接池增加validationQuery: SELECT 1分页数据不准PageHelper在查询前执行了无关SQLPageHelper.startPage()必须紧跟Page查询方法前端控制台报跨域直接访问后端地址没走代理所有请求统一走/api开头不再直连后端npm install卡住网络访问npm源慢换npmmirror源删除node_modules重新安装排错方法上我推荐一个非常实用的顺序先看后端控制台日志再看前端浏览器的Network请求面板最后再排查代码。后端日志能告诉接口到底有没有被调用到Network面板能告诉请求到底发没发出去、返回了什么东西。很多人一上来就怀疑代码逻辑结果查了半天发现是代理没生效白白浪费时间。后端日志在开发环境一定要打印SQL语句MyBatis配置一下mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImplSQL打印出来就能看出PageHelper分页对不对、关联查询的字段是不是有歧义排错效率翻倍。5. 容器化部署Docker Compose一键拉起整套环境5.1 后端镜像构建要点项目开发完成部署环节我直接用Docker Compose编排整套环境。先写后端Dockerfile采用多阶段构建先把Maven打出来的jar包放到最终镜像里镜像体积能小一半以上FROM maven:3.8-jdk-8 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests FROM openjdk:8-jre-slim WORKDIR /app COPY --frombuilder /app/target/pet-care-api.jar ./app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]多阶段构建的好处是把编译工具链和运行环境隔离。最终镜像里只有JDK和jar包不包含Maven一方面体积小了另一方面攻击面也小了。运行参数里有个细节SpringBoot应用的配置文件如果包含数据库密码不要把密码写进镜像。运行时通过环境变量注入Compose文件里给SPRING_DATASOURCE_PASSWORD这样的环境变量然后YAML配置用占位符读取spring: datasource: password: ${DB_PASSWORD}5.2 Nginx配置与Compose编排前端用Nginx跑构建完静态文件后打包进Nginx镜像。history路由模式必须配置try_files规则否则用户刷新页面会404。因为刷新时Nginx直接找对应路径的静态文件找不到就404了需要让Nginx把请求回退到index.html由前端路由接管。server { listen 80; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://pet-care-api:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }注意proxy_pass http://pet-care-api:8080/;后面的斜杠是关键。带斜杠表示把/api前缀剥掉再转发不带斜杠就会把完整路径带过去。Compose的服务名pet-care-api在Docker内部网络中可以直接当作主机名解析。最后docker-compose直接管理三个服务services: mysql: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: pet_care volumes: - ./sql:/docker-entrypoint-initdb.d ports: - 3306:3306 pet-care-api: build: ./backend environment: DB_PASSWORD: root123 depends_on: - mysql pet-care-web: build: ./frontend ports: - 80:80 depends_on: - pet-care-apiMySQL初始化脚本放在./sql目录容器首次启动时会按文件名顺序自动执行建库建表脚本。Compose的方式只用一条docker-compose up -d命令就能把数据库、后端、前端全部跑起来新手照着做基本不会出大乱子。部署完成后可以用docker-compose logs -f pet-care-api看后端日志接口报错排查跟本地开发一样看堆栈信息即可。个人经验是把日志重定向到文件方便排查历史问题。做这套系统的过程里我个人体会最深的一件事是前后端分离项目最大的成本不在写代码而在约定和规范。数据模型提前规划清楚接口返回结构统一鉴权链路在前后端同时做校验这类底层决策定下来后面的开发和部署都是顺水推舟的事。最后再分享一个小技巧所有接口在开发阶段一定要在真实数据库上测一遍不要只在内存数据库里自测通过就完事语法兼容性和SQL索引的差异在MySQL里踩一次坑就够你心疼半天。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询