news 2026/10/6 12:52:52

SpringBoot+Vue宠物健康管理系统:前后端分离项目实战与部署详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue宠物健康管理系统:前后端分离项目实战与部署详解

做这套宠物健康顾问系统的时候,我身边好几个养猫养狗的朋友都跑来问:疫苗到底什么时候打、驱虫多久一次、体检报告散落几张该怎么整理?问的人多了,我干脆把平时积累的 SpringBoot + Vue + MyBatis + MySQL 这套前后端分离技术栈用上,宠物档案、疫苗记录、驱虫管理、体检报告、下次项目提醒、健康顾问建议一起做进一个系统里。它既能帮普通养宠人把健康数据管起来,也是一个完整可复现的 Java 全栈练手项目——后端是 SpringBoot 提供 RESTful API,前端是 Vue 单页应用,持久层走 MyBatis,数据落在 MySQL,部署教程也一并整理好。

这套东西适合谁?正在找前后端分离项目实战的同学、准备毕业设计的本科生、想系统走一遍从建表到上线全流程的转行朋友,都有参考价值。我尽量不讲空话,每个模块都按我实际开发时踩过的坑、验证过的写法来写。

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

1.1 为什么选前后端分离,而不是传统单体 JSP

先聊一个最基础的问题:宠物健康管理系统这种体量的项目,为什么非要前后端分离?我见过不少入门项目还在用 JSP 方案,页面和后端代码揉在一起,改一个按钮样式要重启整个应用,前端设计师和后端开发互相等对方,效率确实低。

前后端分离的核心逻辑是:后端只负责出 JSON 数据,前端只负责渲染页面和交互。两边通过 HTTP 接口通信,谁都不管对方内部怎么实现。放到这个宠物健康系统里,好处非常具体:

  • 开发阶段前端用 Vite 开发服务器,后端用 SpringBoot 内嵌 Tomcat,两边独立启动,并行开发。
  • 部署阶段前端打包成纯静态文件交给 Nginx,后端打成一个 jar 包独立运行,互不干扰。
  • 后续想加微信小程序端或者安卓 App,直接复用现有 API,前端完全重写都不影响后端。

用生活化的话说:前端是餐厅前台,负责点菜上菜;后端是后厨,负责把菜做好。前后端分离就是把这两个区域彻底隔开,前台装修不影响后厨炒菜。

1.2 SpringBoot + Vue + MyBatis + MySQL 这套组合的取舍

技术选型我比较务实,谁成熟用谁,谁踩坑少用谁。

后端选 SpringBoot,理由很简单:约定大于配置,Starter 机制把大部分样板配置都吞掉了,一个注解就能启动 Web 服务。我用的是 SpringBoot 2.7.x + JDK 8 这个组合,有人可能问为什么不上 3.x,这里多说一句——3.x 要求 JDK 17,并且很多老 MyBatis 插件、代码生成器还没完全跟上,对新手项目而言没必要冒险。网络热词里那句"springboot版本太高",说的就是这个场景:版本追新,结果依赖冲突、配置不兼容,光折腾环境就耗掉半天。

持久层选 MyBatis 而不是 Spring Data JPA,是因为宠物健康系统里涉及不少统计查询和动态条件筛选,比如"查某只宠物所有即将到期的疫苗记录""按月统计驱虫记录数量"。MyBatis 的 XML 映射文件可以精确控制 SQL,动态 SQL 标签拼条件也直观,出问题我能直接看到 SQL 本身,排查成本低。JPA 虽然省代码,但复杂查询的调试成本高,小型系统里优势不明显。

前端选 Vue,因为生态稳定、上手曲线平缓。我具体用的是 Vue 3 + Vite + Element Plus + Pinia,这套组合现在资料最多,遇到问题搜一下基本都有答案。如果求更加保守,Vue 2 + Vue CLI 也完全能实现,本项目的核心逻辑不依赖 Vue 版本特性。

数据库选 MySQL 5.7.44,稳定、资料多。如果你机器上已经装了 MySQL 8.0 也没问题,项目里没有用到只有 8.0 才支持的特性,兼容性很好。

1.3 系统模块与数据闭环设计

宠物健康顾问系统不像电商系统那样功能庞杂,它的核心价值是形成一条完整的健康数据闭环。我规划了四个核心模块加两个辅助模块。

核心模块:宠物档案管理,负责维护宠物基础信息,包括品种、生日、体重、绝育状态;健康记录管理,负责疫苗、驱虫、体检、就诊四类记录的增删改查;提醒中心,根据健康记录自动算出下次疫苗、驱虫的到期时间,按状态展示;健康顾问建议,根据宠物档案和最近记录生成提示文字,比如"该打猫三联加强针了""体重偏重,建议控制喂食量"。

辅助模块:用户登录注册,普通用户只能看到自己的宠物数据;系统管理后台,管理员可以查看所有用户和宠物统计信息,用于展示完整的管理端写法。

数据闭环是这样的:用户建档 → 录入疫苗/驱虫/体检记录 → 系统计算下次到期时间 → 提醒中心展示 → 用户带宠物去完成 → 再录入新记录。每一步都有数据承接,不会出现"录完就扔在那儿"的情况。

2. 后端核心实现与数据库设计

2.1 SpringBoot 工程搭建与分层结构

后端工程我用 Maven 管理,包结构按常见分层拆,不要图省事把所有类堆在一起。我的目录是这样的:

com.pet.health ├── controller # 接口层,只做参数接收和结果包装 ├── service # 业务层,处理业务规则 ├── mapper # MyBatis 数据访问接口 ├── entity # 数据库实体类 ├── dto # 前端传参和返回结果对象 ├── config # 配置类,如跨域、定时任务 └── common # 统一返回结果、异常处理、工具类

启动类上记得加@MapperScan("com.pet.health.mapper"),否则 MyBatis 扫描不到 Mapper 接口,启动直接报错。这个注解是新手最容易漏的,漏了之后 Spring 容器里没有 Mapper 的代理对象,Service 注入失败,报错信息还不太直白。

application.yml里的关键配置,我直接列一个可用的版本:

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/pet_health?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: your_password mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.pet.health.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

map-underscore-to-camel-case必须开,这样数据库的record_date能自动映射到实体的recordDate字段,省掉一堆@Results注解。log-impl配置成 StdOutImpl,控制台会直接打印 SQL 和参数,调试阶段非常有用,上线前再关掉。

2.2 MyBatis 持久层:XML 映射与动态 SQL 的实战写法

MyBatis 的核心玩法是 Mapper 接口配 XML 文件。接口里定义方法,XML 里写 SQL,两者通过 namespace 和方法 id 绑定。我这个系统里最常用的是动态 SQL,举一个真实场景:健康记录查询页允许用户按类型过滤,还可能按日期范围查,这三个条件都可能为空,不可能为每种组合写一个 SQL。

于是在 XML 里用<where>加<if>标签:

<select id="selectRecordList" resultType="com.pet.health.entity.HealthRecord"> SELECT * FROM health_record <where> <if test="petId != null"> AND pet_id = #{petId} </if> <if test="type != null and type != ''"> AND type = #{type} </if> <if test="startDate != null"> AND record_date &gt;= #{startDate} </if> <if test="endDate != null"> AND record_date &lt;= #{endDate} </if> </where> ORDER BY record_date DESC </select>

<where>标签会自动处理掉第一个条件前面的 AND,这个机制比手动拼接 WHERE 1=1 干净得多。这里必须强调一个新手高频踩坑点:传参一律用#{param},不要用${param}。#{}是预编译占位符,MyBatis 会把它替换成?,由 JDBC 的 PreparedStatement 处理,天然防 SQL 注入;${}是字符串拼接,用户输入了什么就拼什么进 SQL,等于把数据库直接敞开给攻击者。

关于 MyBatis 二级缓存,项目里我只给纯字典类查询开了,比如查询宠物品种列表这种不常变的数据。健康记录这类频繁增删改的表不建议开缓存,因为每次 insert/update 都要手动清理缓存,容易出现"改完了查出来还是旧数据"的诡异问题。默认的开关配置是cacheEnabled=false,保持默认就好。

2.3 健康顾问规则:不用规则引擎,用策略模式就够了

"健康顾问"这个模块听起来高大上,其实落地的规则并不复杂。我实现的时候没有引入 Drools 这种重量级规则引擎,而是用 Java 的策略模式,把不同类型的建议拆成独立策略类,每个类只做一件事。

举个例子,疫苗建议的规则是这样的:猫三联一般打两针基础免疫,间隔 21 到 28 天,之后每年加强一针。当系统查到某只猫的最近一次猫三联记录距今超过 330 天时,生成"建议近期接种猫三联加强针"提示;查不到记录且年龄大于 3 个月,生成"建议尽快完成首针免疫"提示。驱虫规则类似:体外驱虫每月一次,体内驱虫每三个月一次,根据到期时间生成不同紧急程度的建议。

我把这些规则封装成AdviceStrategy接口,每个策略类实现generateAdvice(Pet pet, List<HealthRecord> records)方法,再用一个AdviceContext按宠物类型分发。这样以后想加新的建议规则,写一个新类就行,不用改动旧代码。这个设计是我实际项目里反复验证过的:小规则集用策略模式完全够用,引入规则引擎反而增加部署复杂度和学习成本。

2.4 MySQL 表设计:五张核心表怎么建

数据库表设计是整个系统最容易返工的部分,设计错了后面写代码全是泪。我按业务闭环设计了五张核心表,这里直接给出关键字段和设计思路。

宠物表pet:

CREATE TABLE `pet` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) DEFAULT NULL, `name` varchar(50) NOT NULL, `species` varchar(20) NOT NULL COMMENT 'cat/dog', `breed` varchar(50) DEFAULT NULL, `birthday` date DEFAULT NULL, `weight` decimal(5,2) DEFAULT NULL COMMENT '单位kg', `avatar` varchar(255) DEFAULT NULL, `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

健康记录表health_record:

CREATE TABLE `health_record` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `pet_id` bigint(20) NOT NULL, `type` varchar(20) NOT NULL COMMENT 'vaccine/deworm/checkup/treatment', `title` varchar(100) NOT NULL, `record_date` date NOT NULL, `next_due_date` date DEFAULT NULL, `hospital` varchar(100) DEFAULT NULL, `note` text, `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_pet_type_date` (`pet_id`, `type`, `record_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

另外三张表分别是reminder提醒表、health_advice建议表、sys_user用户表,字段逻辑相似不再展开。设计上有两个经验值得说说。

第一,表与表之间我刻意没有加物理外键,只用普通索引维护逻辑关系。理由很实际:物理外键会在每次 insert/delete 时做额外约束检查,影响写入性能,而且后续如果要分库分表或做数据迁移,物理外键会变成巨大的障碍。业务层面在 Service 里做存在性校验就够了。

第二,联合索引(pet_id, type, record_date)是故意加的。因为系统的高频查询就是"查某只宠物某类型记录并按日期排序",这个联合索引能直接命中,避免文件排序。我在实际数据量到几万条之后,发现这个索引让查询从几十毫秒降到几毫秒,收益非常明确。

3. 前端 Vue 实现与前后端联调

3.1 从零搭建 Vue 工程及环境配置

前端我用 Vue 3 + Vite。环境配置这里说几个容易出问题的地方,都是我在"vue安装及环境配置"这个环节被卡过的事情。

Node.js 版本建议用 18 或 20 LTS 版本,太老或太新都可能跟 Vite 有兼容问题。创建项目的命令很简单:

npm create vite@latest pet-health-web -- --template vue

进入目录后安装核心依赖,我实际用到的就四个:

npm install vue-router@4 pinia axios element-plus

Vite 的配置文件vite.config.js里需要配置开发服务器代理,这是前后端分离联调的关键。如果不配代理,浏览器直接请求http://localhost:8080/api会触发跨域,浏览器拦截响应,接口调不通。配了代理之后,前端开发服务器会把/api开头的请求转发到后端,浏览器看到的只是同源请求,跨域问题在开发阶段直接消失:

export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })

有人问生产环境怎么办?生产环境前端是静态文件,不存在开发代理,跨域一般用 Nginx 反向代理解决,这个放到部署章节细说。

3.2 路由与状态管理:这套系统怎么做页面组织

路由我用 vue-router 4,按模块拆分页面。核心路由表就这几个:登录页、首页仪表盘、宠物列表、宠物详情、健康记录新增/编辑、提醒中心、健康顾问建议。

这里说个判断:很多网上项目喜欢宣传"动态路由",按用户权限动态生成菜单,但本系统用户角色就两种,普通用户和管理员,路由差异很小,动态路由纯属给系统加复杂度。我选择的是静态路由 + 路由守卫的方案。路由守卫负责拦截未登录用户,每次跳转前检查 localStorage 里的 token,没有就重定向到登录页。这个写法 20 行以内搞定,清晰可靠。如果你以后想参考若依框架那种前后端分离版本里的动态路由实现,那是大项目里的权限体系,小系统不必照搬。

状态管理用 Pinia,比 Vuex 简洁很多。我只在 store 里维护两个东西:用户信息和登录状态。token 存放在 localStorage,请求前加到 header。Pinia 的写法非常直观:

const useUserStore = defineStore('user', { state: () => ({ token: localStorage.getItem('token') || '', userInfo: null }), actions: { setLogin(data) { this.token = data.token this.userInfo = data.userInfo localStorage.setItem('token', data.token) }, logout() { this.token = '' this.userInfo = null localStorage.removeItem('token') } } })

3.3 API 封装与跨域联调

前后端分离项目里,前端所有请求应该走统一的 API 封装,而不是在每个页面里直接调 axios。我的做法是新建src/api/request.js,创建一个 axios 实例,统一配置 baseURL、超时时间、请求拦截器和响应拦截器:

import axios from 'axios' const request = axios.create({ baseURL: '/api', timeout: 10000 }) request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = 'Bearer ' + token } return config }) request.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { return Promise.reject(new Error(res.message || '请求失败')) } return res.data }, error => { if (error.response && error.response.status === 401) { window.location.href = '/login' } return Promise.reject(error) } )

后端接口路径统一以/api开头,Controller 上我加了一个类级别的@RequestMapping("/api/health-record")。这样开发环境的 Vite 代理、生产环境的 Nginx 反代都只处理/api前缀,规则统一,不容易出错。

后端跨域我也配了全局 CORS 配置作为兜底,防止有人跳过代理直接访问后端。两种方案同时保留并不冲突,前端代理优先生效,后端 CORS 作为保障。

3.4 核心页面与组件实现

页面实现上,我分了几个关键组件。

宠物档案列表页用卡片式布局,每张卡片展示宠物头像、名字、品种、年龄、体重和最近一次健康记录,右上角是"查看详情"和"新增记录"按钮。宠物详情页用了 Element Plus 的时间线组件,把疫苗、驱虫、体检记录按时间倒序展示,每条记录后面跟着一个"下次到期"标签,到期时间在一个月内显示黄色,已过期显示红色。这个视觉反馈非常直观,用户打开页面第一眼就知道该做什么。

健康记录表单页是数据准确性最关键的地方,表单校验做得很细。日期必填、类型必选、下次到期日期不能早于本次日期,这些规则我全部在表单层面强制校验,避免脏数据进数据库。体重字段我做了一个联动建议:检测到连续两次体重记录增长超过 15% 时,在表单下方提示"体重增长较快,建议关注饮食结构"。

ECharts 我用来做首页仪表盘的体重趋势折线图,展示宠物最近六次体重变化。这个图对用户判断健康状态很有价值,实现也不复杂,只是把 health_record 里的体重数据取出来按日期画线而已。

4. 完整部署流程与打包细节

4.1 本地开发环境版本选型

先把环境版本统一好,避免"我电脑上能跑"的尴尬。Java 用 8 或 11,Maven 用 3.6 以上,MySQL 用 5.7.44。MySQL 安装这里单独提一句,Windows 用户安装包版一路下一步很容易,但最后容易忘记选字符集,建议安装时选 utf8mb4,或者装完手动执行ALTER DATABASE pet_health CHARACTER SET utf8mb4;,否则中文可能乱码。

Linux 服务器安装 MySQL 5.7.44 用 rpm 包相对省事,但 rpm 安装完后要手动启动服务并设置 root 密码:

systemctl start mysqld grep 'temporary password' /var/log/mysqld.log mysql -u root -p ALTER USER 'root'@'localhost' IDENTIFIED BY 'YourPassword123!';

这里有个细节经验:MySQL 5.7 默认有 validate_password 插件,初始密码策略要求包含大小写字母数字和特殊字符。如果想用简单密码,需要先执行set global validate_password_policy=LOW;再改密码,否则一直报错。

数据库初始化更直接,用 mysql 命令导入项目里准备好的 SQL 脚本:

mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS pet_health DEFAULT CHARACTER SET utf8mb4;" mysql -u root -p pet_health < pet_health.sql

4.2 后端打成 jar 包部署

后端部署我最常用的是打可执行 jar 包放服务器跑。打包前先确认pom.xml里引入了 spring-boot-maven-plugin,否则打出来的 jar 不能直接运行。打包命令:

mvn clean package -DskipTests

执行完 target 目录下会生成pet-health-server.jar,传到服务器后启动:

java -jar pet-health-server.jar --spring.profiles.active=prod

生产环境的数据库连接我放在application-prod.yml里,和本地配置隔离。因为前面配置里数据源 URL 带了serverTimezone=Asia/Shanghai和useSSL=false,MySQL 8.0 的认证方式默认是 caching_sha2_password,如果你用的驱动版本比较老,可能出现连接报错,解决方案要么换新驱动,要么在 MySQL 里执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';。我建议直接用 MySQL Connector/J 8.x 驱动,省去老兼容问题。

想长期稳定运行,我建议写一个 systemd 服务文件,开机自启,进程挂了自动拉起:

[Unit] Description=Pet Health Server After=network.target [Service] User=root ExecStart=/usr/bin/java -jar /opt/pet-health/pet-health-server.jar --spring.profiles.active=prod Restart=always [Install] WantedBy=multi-user.target

4.3 前端构建产物的两种部署方式

前端打包很简单:

npm run build

dist 目录下就是纯静态文件。部署方式有两种,我都实际用过,各有适用场景,这里对比一下。

方式一:Nginx 托管静态文件加反向代理,这是推荐的生产方案。Nginx 配置里做两件事,一是把 dist 目录作为网站根目录,二是把/api前缀的请求转发到后端服务:

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

try_files这一行是必须的。因为前端用的是 history 路由,直接访问http://your_domain/pet/1时 Nginx 会去找pet/1这个文件,找不到就 404。try_files $uri $uri/ /index.html的含义是:先找文件,找不到就返回 index.html,由前端路由接管。

方式二:把前端打包文件放进 SpringBoot 的src/main/resources/static目录,重新打后端 jar,前后端一个进程跑。这种方式适合毕设演示、内网工具类项目,特点是部署最简单,一个 jar 全部搞定。需要注意前端请求的 baseURL 必须是相对路径/api,不能写死http://localhost:8080,否则生产环境域名变了就要重新打包。

4.4 数据库初始化与提醒任务

提醒功能依赖定时任务。我在 SpringBoot 启动类加@EnableScheduling,然后在ReminderTask里每天凌晨扫一次所有健康记录,把next_due_date在最近七天内的或者已经过期的记录写入提醒表:

@Component public class ReminderTask { @Scheduled(cron = "0 30 2 * * ?") public void scanDueReminders() { List<Reminder> dueList = reminderMapper.selectDueReminders(7); for (Reminder r : dueList) { r.setStatus(0); reminderMapper.updateStatus(r); } } }

定时任务上线时有一个坑我踩过:如果部署了多个后端实例,同一个任务会被执行多次。对提醒这种业务来说,重复写记录会造成数据污染。我的处理方式很朴素,insert 之前先查有没有同宠物同类型的待办提醒,存在就跳过。如果要更严格,可以引入分布式锁,但对本项目来说查重已经够用。

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

5.1 启动阶段问题速查

这一节我整理一下反复被问到的问题,直接对照排查。

端口被占用是最常见的第一道坎。启动报Port 8080 was already in use时,Linux 上用netstat -tlnp | grep 8080找到进程 PID,kill -9 PID干掉;Windows 上netstat -ano | findstr 8080配合任务管理器结束进程。

Mapper 找不到是第二个高频问题。报错Invalid bound statement (not found)时,按三个方向排查:启动类有没有@MapperScan;XML 文件的 namespace 是否跟 Mapper 接口全限定名一致;application.yml里的mapper-locations是否指向classpath:mapper/*.xml。这个错误一般就是这三个原因,我见过 90% 的情况都能命中其中一个。

SpringBoot 版本过高引发的坑也值得单独说。比如 SpringBoot 3.x 把javax包改名成jakarta,很多老项目里 import 的是javax.servlet,升级后直接编译不过。如果你在网上找到的参考代码是 2.x 时代的,而你本地装了 3.x,建议直接降级到 2.7.x,而不是费力改第三方依赖。

5.2 数据库连接与 SQL 问题

数据库连接报错的排查顺序:第一看 MySQL 服务有没有启动,第二看application.yml的 URL、用户名密码对不对,第三看时区和 SSL 配置。这个系统我推荐统一用这串 URL:jdbc:mysql://localhost:3306/pet_health?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai。前两项保证中文不乱码,useSSL=false关掉 MySQL 8.0 默认的 SSL 校验,避免本地没配证书时报 SSL 连接错误,serverTimezone把服务器时区固定为东八区,否则 JDBC 解析时间戳会跟本地时间差 8 小时。

还有一个典型报错是Expression #1 of ORDER BY clause is not in GROUP BY clause,这是 MySQL 5.7 的only_full_group_by模式导致的。我在统计报表查询里就踩过,解决方式有两种,一是在查询里确保 select 的字段都在 group by 里出现,二是修改 sql_mode 去掉only_full_group_by。正规做法是改 SQL,图省事才改配置,建议前者。

5.3 前端构建与路由问题

前端问题主要集中在这几个点。

跨域报错 CORS 时,确认请求是从浏览器直接发到后端,还是走代理。开发环境按 3.1 节配置 Vite proxy 即可;生产环境走 Nginx。很多人开发环境跨域通了,生产环境又说跨域,其实生产环境根本没有跨域,因为前端和/api在同一个域名下,真正的问题是 Nginx 代理没配好。

history 路由刷新 404 是部署中最常见的坑。解决方式就是前面说的try_files $uri $uri/ /index.html。不少项目部署完首页能打开,点进去再刷新就白屏或者 404,八成是这个配置没写。

还有一个细节是打包后接口域名不生效。前端代码里如果写死了http://localhost:8080这种绝对地址,打包换环境后必然报错。正确做法是接口 baseURL 统一用/api这种相对路径,由部署层的代理决定请求发到哪。

5.4 上线后的性能与安全经验

数据量上来之后,提醒中心按next_due_date查询会变慢。我加了一个单列索引idx_next_due_date,配合健康记录的联合索引,扫描范围大幅降低。慢 SQL 排查我直接开 MySQL 慢查询日志,定位到具体语句后基本都能靠索引解决。

安全方面有三件事必须做。一是密码加密存储,绝对不能明文,我用的是 BCrypt,Spring Security 里直接有现成的BCryptPasswordEncoder,比 MD5 靠谱得多,因为自带盐值,相同的密码每次加密结果不同。二是预防 SQL 注入,MyBatis 里全部用#{}预编译,上面已经强调过,这里是底线要求。三是接口权限校验,登录接口之外的业务接口都要检查 token 是否有效,不能只靠前端隐藏按钮,因为接口可以被直接调用。系统里我实现了一个简单的拦截器统一校验Authorization头,没有 token 直接返回 401。

关于安全管理,我的原则是:能力范围内把能做的基础防御做扎实,密码加密、SQL 注入防护、接口鉴权这三道防线守住,小系统就够用了。

这套系统做完之后我最大的体会是:新手做项目,重要的不是功能堆得多花哨,而是把一条主链路从数据库到页面再到部署完整走通。健康记录、提醒、建议这看似简单的三个环节,一旦亲手从建表开始实现到上线,你对 SpringBoot、MyBatis 动态 SQL、Vue 路由守卫、Nginx 代理这些技术的理解会完全不一样。

最后再分享一个实际经验:这套系统做的时候我刻意保持了模块边界清晰,后来有朋友想把宠物诊所的预约功能加进来,我只需要新增预约表和对应接口,原有模块完全不用动。所以如果你也想拿这个项目练手,我建议你把它改造成宠物诊所版,加上预约、病历、收费这些模块,既锻炼了业务设计能力,又能当作品集里一个差异化亮点。

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

WinForms并发任务最佳实践:Parallel.For+SemaphoreSlim+Timer组合

做WinForms开发的人&#xff0c;迟早会碰到这么一类需求&#xff1a;界面上点一个按钮&#xff0c;后台要处理大批量数据&#xff0c;界面不能卡死&#xff0c;进度还得实时可见。我以前接手过一个导入工具&#xff0c;数据量小的时候一切正常&#xff0c;量一大就出现两个问题…

作者头像 李华
网站建设 2026/10/6 12:50:29

波士顿房价预测:从线性回归到XGBoost的机器学习完整流程

先扔个完整代码镇楼。波士顿房价预测是我带新人入门机器学习时必用的项目&#xff0c;它特征不多、量级适中&#xff0c;但你想要的回归流程全都在里面&#xff1a;数据读取、拆分、标准化、训练线性模型、集成模型、评估指标。很多人以为这种经典项目太简单&#xff0c;结果一…

作者头像 李华
网站建设 2026/10/6 12:50:11

9.26云游戏免费兑换码领取攻略:高效获取与批量验证方法

这一期直接看云游戏兑换码。不是讲概念&#xff0c;是给一份能在 9 月 26 日当天直接照着操作的领码清单、验证手段和避坑方法。云游戏平台每天都会放出一批免费兑换码&#xff0c;覆盖新用户注册、老用户回流、版本更新、节日活动、直播平台联合活动等场景。问题在于这些码分布…

作者头像 李华
网站建设 2026/10/6 12:49:54

智能体实战:用工作流与知识库打造求婚策划助手

被求婚这种人生大事困扰时&#xff0c;很多人会上网搜攻略、问朋友、看抖音案例&#xff0c;但得到的答案往往碎片化。有人建议旅游求婚&#xff0c;有人说家里布置更温馨&#xff0c;还有人推荐无人机送戒指——选项越多越不知道怎么选。我就经历过这个阶段&#xff0c;最后干…

作者头像 李华
网站建设 2026/10/6 12:47:19

校园服务平台小程序源码:微信小程序+Java毕设项目实战与避坑指南

简介&#xff1a;这是一套面向计算机、电子信息、数学等专业学生的校园服务平台小程序毕业设计源码&#xff0c;经导师指导并认可&#xff0c;适合正在做毕设、课程设计或期末大作业、需要项目实战练习的学习者参考。项目采用Java技术栈&#xff0c;代码经过严格调试&#xff0…

作者头像 李华
网站建设 2026/10/6 12:47:12

Unity 2021第三人称漫游:从场景搭建到性能优化全流程

简介&#xff1a;这是一份面向Unity初学者与高校学生的期末作业级项目资源&#xff0c;基于Unity2021版本开发&#xff0c;主题为第三人称漫游精美场景。项目围绕角色控制器、3D场景搭建、光照材质、UI系统与输入管理展开&#xff0c;适合正在学习Unity3D游戏开发、需要完成课程…

作者头像 李华