news 2026/10/6 8:39:40

宠物爱心组织管理系统实战:SpringBoot2+Vue3+MyBatis-Plus+MySQL8

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
宠物爱心组织管理系统实战:SpringBoot2+Vue3+MyBatis-Plus+MySQL8

1. 项目概述:为什么我要做一套宠物爱心组织管理系统

宠物爱心组织管理系统在程序员的项目清单里不算新奇,但真正跑到救助站和爱心领养中心看过一圈的人,才知道这套系统解决的全是切肤之痛。从宠物入所建档、疫苗驱虫记录、领养人资质审核,到志愿者排班、物资捐赠入账、活动报名统计,大量信息散落在Excel表格和微信聊天记录里,月底对账能对到半夜。我最近完成的这套 Java Web 宠物爱心组织管理系统,技术栈锁定 SpringBoot2 + Vue3 + MyBatis-Plus + MySQL8.0,源码和配套文档都齐,把上述业务全部搬上系统,适合正在找实战项目的Java开发者、刚入门Vue3的前端同学,以及想推进信息化建设的爱心组织。

这套系统不是那种堆了一堆CRUD就交差的练习项目。宠物爱心组织的业务比一般管理系统更杂:既有常规的档案管理,又有领养这种带审核流和回访周期的业务,还涉及志愿者、捐赠、活动等多个参与方。把这些关系理清楚,本身就是一个完整的业务建模训练。对开发者来说,照着这套源码走一遍,能同时掌握SpringBoot2后台、Vue3前端、MyBatis-Plus通用CRUD、MySQL8数据库设计四条线,比零散看教程效率高得多。

1.1 爱心组织的真实业务痛点

先说线下场景。救助站收容的每一只流浪动物,从进站那天起就要开始记录:体况检查、治疗过程、体内外驱虫、疫苗首免和加强针、绝育手术时间、寄养位置、是否亲人、能不能和其他动物合笼。这些信息以前可能只存在于救助群里的聊天记录和一张手写登记表上。真到了领养人来看猫看狗,志愿者得临时翻群聊记录才能说清楚这只动物的来历。

领养审核更麻烦。救助站要把流浪动物交到陌生人手里,心里那道坎必须过:对方住在哪、是不是租房、家里有没有其他宠物、有没有养宠经验、能否接受回访。这些审核要素要是靠微信截图传来传去,遗漏和扯皮是家常便饭。捐赠和物资对账也难,现金捐、猫粮狗粮捐、药品捐,时间跨度一拉长,Excel里的数据结构就变形了,格式也各不相同。

这套系统把上述问题全部模块化:宠物档案管全生命周期,领养申请走完整审核状态流,捐赠物资分开记账,志愿者排班和活动报名都在线上完成。爱心组织负责人不再需要追着志愿者要表格,所有操作留痕,出了问题能追溯到人,责任边界清楚很多。

1.2 这套系统的学习价值与适用人群

我之所以把源码和文档整理出来,是因为这类"业务真实但不复杂"的管理系统,是目前Java Web学习者最缺的中间态项目。太大的电商项目容易陷在分布式中间件里,太小的学生作业又只有一个表跑通流程,而宠物爱心组织管理系统刚好卡在中间:单体能跑,业务有深度,数据库设计有讲究。

如果你正在准备面试项目,这套系统能讲的东西很多:MyBatis-Plus 如何把单表CRUD工作量压到最低,JWT 如何做无状态登录,领养审核的状态机怎么设计,Vue3 的响应式数据在复杂表单里的实际用法,MySQL8 部署时那些坑是怎么排查的。如果你本身就是爱心组织的技术志愿者,这套源码可以直接改改上线用,文档里连数据库初始化和部署脚本都准备好了。

2. 技术选型拆解:为什么锁定这四件套

选型这件事我吃过亏。早期做管理系统喜欢追新,什么新框架都敢上,结果做到一半官网文档不齐全,卡在环境配置上折腾一周。这次做宠物爱心组织管理系统,我一开始就定了三条纪律:生态成熟、资料充足、团队上手成本低。最终锁定的 SpringBoot2 + Vue3 + MyBatis-Plus + MySQL8.0,恰好都满足。

2.1 SpringBoot2 与 MyBatis-Plus:稳定压倒一切

SpringBoot2 很多人觉得"过时"了,SpringBoot3 都出了好几年。但实际做业务项目,稳定性比版本号更重要。SpringBoot2.7.x 搭配 JDK8,是目前存量项目最多、网上解决方案最全的组合。你随便搜一个报错,几乎都能找到前人踩坑的记录。SpringBoot3 默认要求JDK17,不少老版本的依赖和插件跟不上,迁移成本不低。所以这套系统我坚定用SpringBoot2.7.x,开发效率和排错效率都高。

MyBatis-Plus 是这套系统的效率担当。它的核心价值在于:单表增删改查完全不用写SQL,继承一个 BaseMapper 就有几十个现成方法。更实用的是条件构造器,比如查询所有"待领养"的猫咪,直接 LambdaQueryWrapper 拼接条件,不用在XML里维护字符串SQL。它内置的分页插件、逻辑删除、字段自动填充、代码生成器,每一个都是开发中最常遇到的硬需求。

我选择 MyBatis-Plus 还有一个理由:它的通用 CRUD 服务配合 SpringBoot 的泛型抽象,可以实现无状态的增删改查服务层。简单说,宠物、志愿者、捐赠等模块,只要实体类定义好,Service 层不需要每个模块重复写一遍 create、update、delete、page 方法,一个通用基类全部搞定。后续新增一张表,只需要写个实体类和Mapper接口,业务代码量少到可以忽略。

2.2 Vue3 + Vite:前端工程化的当下最优解

Vue3 相比 Vue2 最大的变化是 Composition API。以前写一个页面逻辑,数据、方法、计算属性分散在 options 里,一个功能相关的代码被拆得七零八落;用 Composition API 之后,可以把某个业务的所有响应式变量和函数放在一起,逻辑内聚性一下子提高了。配合 setup 语法糖,代码读起来和普通 TypeScript 函数几乎没区别,这也是Vue3学习曲线中最值得先掌握的差异点。

组件生态我选了 Element Plus,后台管理系统用它做表格、表单、弹窗、分页就是标准的搭积木流程,开发速度极快。构建工具方面用了 Vite 而不是 vue-cli,因为 Vite 冷启动快到毫秒级,改代码浏览器热更新几乎无感。整个项目没有做复杂的微前端或服务端渲染,一个典型的中后台管理端,用 Vue3 + Vite + Element Plus 已经是效率最高的组合。

很多刚从Vue2迁移过来的同学会纠结 reactive 和 ref 到底用哪个。我的经验很简单:基础类型用 ref,对象和数组如果不需要整体替换用 reactive,但一旦涉及整表替换(比如分页接口返回新数组),直接用 ref 包一层更省心。这个细节我在前端部分还会专门展开。

2.3 MySQL8.0:版本升级的收益与成本

MySQL8.0 已经发布多年,生产环境占有率早就超过旧版本。它相比 MySQL5.7 有几个实打实的升级:默认字符集是 utf8mb4,emoji 和生僻字不会再乱码;新增窗口函数和 CTE(公用表表达式),写排名、同比环比这类统计SQL方便很多;JSON 类型的功能也增强,存储灵活的扩展字段不用再单独建关联表。

代价也有。MySQL8 的默认认证插件是 caching_sha2_password,老版本的驱动和可视化工具直连会报错,需要换 MySQL Connector/J 8.x,或者手动把用户认证方式改回 mysql_native_password。这两条路我都走过,后面部署章节会给出具体命令和配置。另外,MySQL8 对服务器内存的占用明显比5.7高,如果是1核2G的小服务器,需要主动调低 innodb_buffer_pool_size,不然跑起来很容易卡。

数据库安装我强烈推荐一条 Docker 路线:一条命令拉起 MySQL8,数据目录挂载到宿主机,删容器重建数据也不丢。这套系统附带的部署文档里就是这个方案,省去了一堆 Linux 命令兼容性问题。

2.4 整体架构与请求链路

整套系统是标准的前后端分离单体架构。前端 Vue3 项目通过 axios 发起请求,经过 Nginx 反向代理到后端 SpringBoot 应用的 8080 端口。后端由 Controller、Service、Mapper 三层构成,MyBatis-Plus 负责数据库交互,JWT 处理登录状态,用一个统一的 Result 对象包装所有响应。

请求链路大致是这样:浏览器输入账号密码登录,后端校验通过后签发 JWT Token;前端把 Token 存在 localStorage,每次请求在 Authorization 请求头带上;后端拦截器解析 Token,校验通过后放行到对应的 Controller。这套无状态设计的好处是,服务端不用维护 session,后面真要横向扩展多实例,也不用额外处理会话同步。业务数据最终落到 MySQL8,宠物、领养、志愿者、捐赠、活动、用户六类核心数据,通过外键逻辑关联。

3. 核心模块与数据库设计实战

数据库设计是这套系统最花心思的部分。宠物爱心组织管理系统的数据模型和普通进销存还不太一样:它有一个贯穿宠物档案、领养申请、回访记录的"主业务线",同时又有志愿者、捐赠、活动这些相对独立的"辅助业务线"。设计得好,后面写业务代码就不会一直改表结构。

3.1 六大模块的功能拆解

我把系统拆成六个模块,每个模块解决一类具体业务:

模块核心功能使用角色
宠物档案管理入所登记、医疗记录、疫苗驱虫、绝育信息、状态变更管理员、志愿者
领养管理领养申请、资质审核、家访排期、签约领养、回访跟踪管理员、领养人
志愿者管理志愿者注册、值班排班、服务时长统计管理员、志愿者
捐赠物资管理现金捐赠登记、物资入库、物资出库、库存盘点管理员
活动管理活动发布、报名统计、签到管理、活动回顾管理员、志愿者
系统管理用户管理、角色权限、登录日志管理员

宠物档案可不是简单记个名字照片。我实际建模时给它挂了医疗记录子表,一只流浪猫进站后,治疗过程中的体温、用药、手术记录都要按时间排好,领养人问起来能直接展示完整就医历史。这个细节对爱心组织的信任感提升很关键。

捐赠模块我做了现金和物资分离。现金走金额字段,精确到小数点后两位;物资走独立明细表,记录物品名称、数量、单位、有效期,出库时对应到领养人或活动消耗。不做成一个混合表的好处是,月底对账时现金账和实物账互不干扰,也不会出现一条记录里既放金额又放数量导致统计混乱。

3.2 表结构设计中的几个关键决策

核心表设计上,第一张是宠物表 pet,字段包括:id、pet_name、species(猫/狗)、breed、gender、age、health_status、adopt_status、photo_url、remark、create_time、update_time、deleted。其中 adopt_status 是领养状态,用整数枚举表示,0-待领养、1-审核中、2-已领养、3-已下架。用整数不用字符串,是为了查询和统计效率,配合状态字典表解释数字含义。

第二张是领养申请表 adoption_application,这是业务最复杂的表:id、pet_id、adopter_id、apply_time、home_type(自有/租房)、has_pet_experience、has_other_pets、audit_status、reject_reason、audit_time、follow_up_record。审核状态我用单独字段 audit_status 而不是直接在 pet 表上改状态,因为一次领养申请可能被驳回,但宠物还是待领养状态,两个概念不能混。

设计时有几个决策特别想说。第一,所有表都加了逻辑删除标志 deleted,不用物理删除。救助站的数据有追溯价值,志愿者手滑删错一条记录影响很大,逻辑删除可以随时恢复。第二,表之间不建物理外键,只保留逻辑关联和索引。物理外键在并发写入时容易引发锁竞争,而且以后拆库拆表都得先删外键,业务代码里控制好引用关系就行。

第三,时间字段统一用 DATETIME 并设置默认 CURRENT_TIMESTAMP,不用字符串存时间。字符串时间排序比较都别扭,日期函数也用不上。第四,涉及金额的字段一律 DECIMAL(10,2),用 DOUBLE 会出现0.1+0.2不等于0.3的精度问题,做财务对账是绝对不能接受的。

3.3 领养流程状态机:让审核不乱套

领养审核是整个系统业务流程最重的部分,我设计了一个六步状态机:待审核 → 初审通过 → 家访排期 → 复审通过 → 签约领养 → 回访跟踪 → 归档。驳回可以在多个环节发生,但每次驳回必须填写原因,原因会写入 reject_reason 字段并展示给领养人。

为什么要把一个简单的"通过/拒绝"搞这么复杂?因为真实救助站的审核过程就是分阶段的:管理员先看申请资料是否完整,这是初审;志愿者上门看居住环境,这是家访;负责人综合评估双方匹配度,这是复审;签完领养协议后,还要定期回访确认宠物在新家过得好不好。每个环节的责任人不同,权限也不同。用状态机建模,每个状态对应一个操作按钮集合,系统自然就不会出现"跳步审核"或"重复审核"的情况。

状态机的实现不复杂,我在后端 Service 里封装了一个 audit() 方法,传入当前状态、目标状态和操作人角色,先校验状态转移是否合法,再执行更新。SpringBoot 的事务注解保证审核记录和状态更新要么同时成功,要么同时回滚。这套逻辑面试时也很好讲:状态机 + 事务 + 权限校验,三个点合在一起,就是一个完整的业务闭环。

4. 后端手把手实现:SpringBoot2 + MyBatis-Plus

后端部分是这套系统的重头戏。我用 SpringBoot2.7.18 搭建工程,JDK8,打包方式为 jar。下面从初始化、通用CRUD封装、登录认证三个层面说清楚实现思路。

4.1 项目初始化与关键依赖配置

新建工程时我直接选了 Spring Initializr 生成,Java 8、打包方式 jar、SpringBoot 版本 2.7.18。核心依赖在 pom.xml 里这样加:

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.2</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.25</version> </dependency> </dependencies>

这里特别提醒一点: MySQL8 对应的驱动依赖坐标是com.mysql:mysql-connector-j,而不是以前5.7时代的mysql:mysql-connector-java。如果你用了旧坐标,即使代码能跑起来,也可能因为驱动版本太老导致认证方式不兼容。

核心配置文件 application.yml 里,数据源和 MyBatis-Plus 的配置是这样:

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/pet_aid?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: 123456 mybatis-plus: global-config: db-config: id-type: auto logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 configuration: map-underscore-to-camel-case: true mapper-locations: classpath:/mapper/**/*.xml

url 参数里有两个关键点:一个是serverTimezone=Asia/Shanghai,不配的话 JDBC 连接时会报时区异常;另一个是allowPublicKeyRetrieval=true,这是 MySQL8 的 caching_sha2_password 认证插件需要的,新驱动默认不主动获取服务端公钥,不配置会报连接被拒绝。这两个参数几乎是 MySQL8 + JDBC 的标配,建议直接背下来。

4.2 基于 MyBatis-Plus 的通用 CRUD 封装

MyBatis-Plus 的高效用法不是每个模块写一套 CRUD,而是做一次通用封装,后续直接复用。我先定义 BaseEntity,所有业务表对应的实体都继承它:

@Data public class BaseEntity { @TableId(type = IdType.AUTO) private Long id; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; @TableLogic private Integer deleted; }

createTime 和 updateTime 是自动填充字段,值由 MetaObjectHandler 统一生成,不需要业务里手动 set。这里有一个容易踩的坑:用了 @TableLogic 逻辑删除后,框架会自动在查询 SQL 后面追加AND deleted=0,这是对的,但如果你在业务里又自己写了带 deleted 条件的 SQL,就会造成条件重复,属于典型的通用封装搭配问题。所以我后面自定义 SQL 时,明确约定不再重复拼 deleted 条件。

自动填充处理器这样写:

@Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } @Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } }

由于所有实体都有 create_time、update_time、deleted 三个字段,我直接用 BaseEntity + 自动填充统一接管,业务代码里完全不用碰时间字段。后续新建一张表,实体继承 BaseEntity,CRUD 能力就全有了,这就是通用 CRUD 服务的基础。

分页功能不需要每次手写 limit,配置一个分页拦截器即可:

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

有了这个配置,Controller 里接 Page 参数,Service 里 this.page(page, wrapper) 就能完成分页。这里要强调:MyBatis-Plus 的分页插件不配置就不生效,很多新手只引入了依赖却不注册这个 Bean,结果分页查询始终返回全部数据,排查半天才发现是拦截器没加。

4.3 登录认证与权限控制

管理系统的登录认证,我选择 JWT 方案。不需要 Redis 存 session,服务端无状态,逻辑也简单。登录接口收到用户名密码后,去 user 表查询并校验密码,通过就生成一个有效期 2 小时的 Token,把用户ID和角色编码塞进 claims 里。

Token 工具类核心逻辑:

public class JwtUtil { private static final String SECRET = "pet-aid-system-secret"; public static String generateToken(Long userId, String role) { return Jwts.builder() .claim("userId", userId) .claim("role", role) .setExpiration(new Date(System.currentTimeMillis() + 2 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); } }

权限拦截器里面,除了验证 Token 是否有效,还要针对不同接口做角色校验。比如领养审核接口只有 ROLE_ADMIN 能调用,志愿者排班接口 ROLE_VOLUNTEER 也能调用。判断逻辑简单直接:从 Token 解析出的 role 字段,和接口要求的角色做匹配。这里我建议用注解的方式实现,写一个 @RequireRole 注解挂在 Controller 方法上,拦截器反射读取注解统一校验,业务代码里就不用到处判断角色了。

统一响应体和全局异常处理是我强烈建议加的。所有接口返回 Result 结构,里面 code、msg、data 三个字段,成功 code=200。全局异常处理器把业务异常、参数校验异常、系统异常分开处理,避免堆栈信息直接裸奔到前端。这一层做好了,前端拿到任何响应都能用一个拦截器统一处理错误提示,开发体验提升非常明显。

5. 前端手把手实现:Vue3 + Element Plus 后台

前端部分用 Vue3 + Vite + Element Plus 搭建。我按后台管理系统常用的组织方式:views 放页面、router 管路由、api 层统一封装网络请求、stores 放 Pinia 状态。下面挑三个关键环节讲。

5.1 Vite 初始化项目与基础设施搭建

创建项目我用的命令是:

npm create vue@latest

这个命令会生成一个带 TypeScript、Vue Router、Pinia 选项的基础工程。生成之后安装依赖:

npm install element-plus axios pinia vue-router npm install -D unplugin-auto-import unplugin-vue-components

Element Plus 我按需自动导入,用 unplugin-vue-components 里的 ElementPlusResolver,这样打包体积比全量引入小很多。对后台项目来说,这一步收益很明显,首屏加载速度快,不会因为把整个组件库打进去导致白屏时间长。

axios 请求封装我这里给出一个可复用的雏形:

import axios from 'axios' const service = axios.create({ baseURL: '/api', timeout: 10000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) service.interceptors.response.use( res => { const { code, msg, data } = res.data if (code === 200) return data ElMessage.error(msg) return Promise.reject(new Error(msg)) }, err => { if (err.response?.status === 401) { localStorage.removeItem('token') router.push('/login') } return Promise.reject(err) } )

注意 axios 拦截器里处理 401 跳转登录,这是后台系统必须有的兜底逻辑。Token 过期、被篡改、权限不足,后端统一返回 401,前端收到后清理本地登录态并跳回登录页,用户重新登录一次即可恢复,不用等浏览器报错才反应过来。

5.2 宠物信息管理页面的完整实现

宠物管理页面是典型的"搜索区 + 表格 + 分页 + 弹窗表单"结构。搜索区按宠物名、物种、领养状态筛选,表格展示主要字段,行内操作点"编辑"弹出表单。

关键代码片段:

<template> <el-table :data="tableData" v-loading="loading"> <el-table-column prop="petName" label="宠物名" /> <el-table-column prop="species" label="物种" /> <el-table-column prop="breed" label="品种" /> <el-table-column prop="age" label="年龄" /> <el-table-column label="状态"> <template #default="{ row }"> <el-tag :type="statusMap[row.adoptStatus]">{{ statusText(row.adoptStatus) }}</el-tag> </template> </el-table-column> <el-table-column label="操作"> <template #default="{ row }"> <el-button type="primary" link @click="openEdit(row)">编辑</el-button> </template> </el-table-column> </el-table> <el-pagination v-model:current-page="query.pageNum" v-model:page-size="query.pageSize" :total="total" @change="loadData" /> </template>

表单校验用 el-form 的 rules,注意编辑和新增共用同一个弹窗时,打开弹窗后要用 nextTick 清空下旧的表单校验状态。我踩过这个坑:编辑完一条数据,关掉弹窗再点新增,表单还残留着上次的校验红字。解决办法是在 open 方法里调用 formRef.value.resetFields(),并且在 dialog 关闭后重置表单数据。

图片上传这块,我本地开发用的是后端接口接收 MultipartFile,保存到配置的上传目录,然后返回文件访问路径存入 photo_url。部署上线如果条件允许,换成 OSS 或者 MinIO 都行,代码上只是替换上传接口返回的 URL,前端逻辑不用动。

Vue3 数据这里有个细节值得说。加载列表数据时用const tableData = ref([]),接口返回后整体赋值tableData.value = res.rows。如果用 reactive 包数组,整体赋值会丢失响应性,很多人第一次从 Vue2 转到 Vue3 都会在这个地方卡住。我的建议就是列表数据统一用 ref,简单直接。

5.3 领养审核交互的细节打磨

领养审核页面是整个系统前端交互最复杂的地方。列表展示所有领养申请,每行根据 audit_status 显示不同按钮:待审核显示"通过""驳回",初审通过显示"安排家访",复审通过显示"签约领养"。这些按钮不能一次性全显示,否则志愿者误点跳步,业务流程就乱套了。

点"通过"前我加了一个二次确认弹窗,文案写明当前环节和后续环节。比如初审通过后提示"将由管理员安排家访",让操作人明确知道下一步是什么。驳回时表单里必须有驳回原因,内容不为空才允许提交,这个校验用 el-form 的 rules 就能实现。

审核详情我用了抽屉组件 el-drawer,把领养人的住房信息、养宠经验、当前宠物情况汇总展示。为了避免审核时看不全信息还要跳转多个页面,我在详情抽屉里做了个 Tab,第一个面板显示申请资料,第二个面板显示宠物档案摘要,第三个面板显示历史审核记录。这样审核人一个页面看完所有上下文,再做决定,符合实际业务操作习惯。

前端还有个细节,就是状态变更后列表刷新。我统一在操作成功的回调里调用 loadData(),而不是手动去改某一行数据。虽然多一次请求,但保证列表和服务端数据绝对一致,不会出现本地改了状态但漏刷新字段的问题。

6. MySQL8.0 安装与项目部署:一条 Docker 路线搞定

这一节解决两个问题:MySQL8.0 怎么装、系统怎么部署上线。两个问题都给出我实际验证过的步骤。

6.1 Docker 安装 MySQL8.0 与初始化配置

在 Linux 服务器上安装 MySQL8.0,我最推荐 Docker 方式。命令如下:

docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -e MYSQL_DATABASE=pet_aid \ -v /opt/mysql8/conf:/etc/mysql/conf.d \ -v /opt/mysql8/data:/var/lib/mysql \ mysql:8.0

参数解释一下:-p 3306:3306 把容器内 3306 端口映射到宿主机;-e MYSQL_ROOT_PASSWORD 设置 root 密码;-e MYSQL_DATABASE 自动创建 pet_aid 数据库;-v 挂载目录最关键,数据目录挂载到宿主机 /opt/mysql8/data,以后容器删了重建,数据还在。很多新手部署 MySQL8 后容器一删数据全没了,就是因为没挂载数据卷。

容器启动后,执行数据库初始化脚本:

docker exec -i mysql8 mysql -uroot -p123456 < init.sql

init.sql 里面包含建表语句、初始管理员账号、状态字典数据。文档里整理了完整 SQL 脚本,直接执行即可,不需要手工逐条建表。

MySQL8 跑在 Docker 里有几个配置要提前注意。第一,时区。如果业务要按北京时间统计捐赠金额,容器默认用 UTC 时区会差 8 小时,要么在 run 命令加-e TZ=Asia/Shanghai,要么在 MySQL 配置里写default-time-zone='+08:00'。第二,字符集。建表语句统一指定DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci,避免中文乱码。第三,内存。小服务器建议在挂载的配置目录里放一个 my.cnf,把 innodb_buffer_pool_size 调到 128M 左右,不然 2G 内存的机器跑 MySQL8 加上 SpringBoot,容易 OOM。

6.2 前后端部署与上线要点

后端打包部署:

mvn clean package -DskipTests java -jar target/pet-aid-server-1.0.0.jar --spring.profiles.active=prod

建议在 resources 下准备 application-prod.yml,生产环境数据库地址、账号密码单独配置,不要把开发环境的配置直接带到服务器上。正式跑服务不要用java -jar挂在前台,用 systemd 或者 Docker,进程挂了能自动重启。

前端打包:

npm run build

构建产物在 dist 目录,把这个目录上传到服务器后,配置 Nginx 反向代理即可:

server { listen 80; server_name your-domain.com; root /opt/pet-front/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }

这个配置里 try_files 必须有,因为 Vue3 用的 history 路由模式,刷新非首页路径时 Nginx 会找不到对应文件,必须回退到 index.html 让前端路由接管。这是前端部署最常见的坑,没有之一。/api/前缀的请求反代到后端 8080,这样前端请求就同源了,也不会有跨域问题。

7. 常见问题排查与避坑实录

整套系统从开发到部署,我踩过不少坑,这里整理成速查表,按后端、前端、数据库三个维度列出来。

7.1 MyBatis-Plus 高频问题

现象原因解决方案
分页查询返回全部数据没注册分页拦截器添加 PaginationInnerInterceptor Bean
逻辑删除后数据查询不到自定义SQL又拼了deleted条件自定义SQL不再重复写deleted
自动填充字段为nullMetaObjectHandler 没注入或字段名不一致检查处理器类是否有@Component
条件构造器报字段不存在字段名写成了数据库下划线形式用 LambdaQueryWrapper 避免拼字符串
批量插入效率极慢JDBC URL没开批处理url追加 rewriteBatchedStatements=true

批量插入这个点值得多说一句。MyBatis-Plus 的 saveBatch 默认走 JDBC 批处理,但 MySQL 的驱动默认把批处理语句逐条发送,加了rewriteBatchedStatements=true后才能真正合并执行。我在导入志愿者名单时实测过,5000条数据从几十秒优化到两三秒。

7.2 Vue3 开发中的典型坑

Vue3 和 Vue2 开发习惯差异不小。最典型的是响应式丢失问题:用 reactive 定义的数组,从接口拿到新数据后直接list = res.data,页面不更新。这是因为 reactive 里的数组引用没变,改变的是它指向的地址。对策有两种:数组用 ref 定义,整体赋值用.value;或者用list.splice(0, list.length, ...res.data)原数组原地更新。我所有列表查询统一用 ref 方案,避免记错。

Element Plus 表单校验也有坑。await formRef.value.validate()在校验失败时是抛异常的,如果不用 try/catch 包住,点击提交后控制台会报 Uncaught (in promise)。正确写法:

const submit = async () => { try { await formRef.value.validate() // 校验通过,调用保存接口 } catch { // 校验失败,什么都不用做,el-form 已经显示红字 } }

我还在一个地方栽过跟头,就是 el-table 的多选。表格设置 type="selection" 列后,跨页勾选需要 row-key 属性配合@selection-change事件维护已选数组,不然翻页后前面勾选的记录不会被计入。对需要批量审核领养申请的场景,这个细节必须处理。

7.3 MySQL8.0 兼容问题速查

现象原因解决方案
连接报 Public Key Retrieval is not allowedcaching_sha2_password 插件机制url加 allowPublicKeyRetrieval=true
连接报 Unable to load authentication pluginJDBC驱动版本过旧使用 mysql-connector-j 8.x
中文乱码字符集不是utf8mb4建库建表显式指定utf8mb4
容器重启数据丢失没挂载数据卷-v 挂载 /var/lib/mysql
服务器内存占用过高innodb_buffer_pool_size 默认值大调低到128M~256M

关于 MySQL8 的认证插件,如果团队里有老版本可视化工具连不上,临时解决办法是执行:

ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';

这条命令把 root 的认证插件改成老版本的 mysql_native_password,但新项目我建议不要这样做,毕竟新驱动和工具都支持 caching_sha2_password,保住默认认证方式的加密强度更稳妥。

部署完成后,记得做两件事:第一,给 root 设置强密码并创建专用业务账号,不能所有环境都用 root;第二,配置数据库定期备份,至少每天凌晨全量备份一次,用 mysqldump 结合 crontab 就能实现。宠物爱心组织的数据积累起来之后,每一笔领养、捐赠记录都是组织的宝贵资产,备份这步不能省。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 8:39:02

集群脑裂与多数派Quorum选举:分布式系统一致性的关键防线

凌晨一点半&#xff0c;监控大屏突然炸了。业务群里有人喊“分布式锁失效了”&#xff0c;后台日志里出现了两个节点同时认为自己才是集群主节点的记录。等网络抖动恢复&#xff0c;数据一比对&#xff0c;才发现同一把锁被两边各写了一次&#xff0c;关键业务字段互相覆盖&…

作者头像 李华
网站建设 2026/10/6 8:37:15

CentOS 7性能调优实战:从内核参数到MySQL优化完整指南

接手过不少“莫名其妙卡得要死”的CentOS 7服务器&#xff0c;第一反应基本都是加内存、换硬盘、重启大法三连。但做多了就会发现&#xff0c;很多问题不是硬件不够&#xff0c;而是系统装好之后一直用默认配置在硬扛。CentOS 7虽然已经进入生命周期尾声&#xff0c;可大量存量…

作者头像 李华
网站建设 2026/10/6 8:37:06

SX1276 LoRa驱动移植与调试全攻略:从寄存器到收发状态机

简介&#xff1a;这是一份面向物联网开发者的LoRa无线通信源代码资源&#xff0c;核心围绕SX1276芯片驱动与LoRaBase基础框架&#xff0c;适合需要实现低功耗、远距离数据传输的嵌入式工程师、学生或物联网项目开发者参考。压缩包共480个文件&#xff0c;以233个C语言头文件&am…

作者头像 李华
网站建设 2026/10/6 8:36:07

TCP传输层核心机制详解:可靠传输、滑动窗口与拥塞控制

最近在读伯克利的CS168课配套textbook——Peterson与Davie合著的《Computer Networks: A Systems Approach》&#xff0c;读到传输层这一章时忍不住放慢了速度。这书和国内常见的“自顶向下”风格不一样&#xff0c;它讲原理喜欢从“为什么必须这么设计”切入&#xff0c;尤其对…

作者头像 李华
网站建设 2026/10/6 8:36:07

防火卷帘控制系统:从联动调试到故障排查实战指南

简介&#xff1a;该文档为XX•金融中心项目机电系统技术规格说明书消防系统第六章“防火卷帘控制系统”的完整技术文本&#xff0c;面向建筑机电工程师、消防系统承包商及设备供应商&#xff0c;用于规范和指导防火卷帘控制系统的供应、安装、调试与验收。内容涵盖垂直开关式、…

作者头像 李华
网站建设 2026/10/6 8:35:52

AI论文网站实操指南:9个工具覆盖选题到答辩全流程

1. 毕业论文卡住成年人的&#xff0c;从来不是智商&#xff0c;而是这四件事先聊个我最近遇到的真实场景。一位已经工作七八年的学生跟我抱怨&#xff1a;白天单位一堆事&#xff0c;晚上回家孩子刚哄睡&#xff0c;打开电脑对着一个空文档发半小时呆。他倒不是不会写&#xff…

作者头像 李华