FEATURED · 精选文章

SpringBoot+Vue宠物领养救助平台毕业设计:业务状态流转与权限控制实战

发布时间 / 2026/9/18 19:25:48
来源 / 创域科博编辑部
栏目 / 资讯中心
SpringBoot+Vue宠物领养救助平台毕业设计:业务状态流转与权限控制实战 简介一份面向计算机专业毕业生的完整毕业论文文档围绕SpringBootVue框架的宠物领养救助平台展开采用B/S架构与MySQL数据库适合需要完成毕设选题、系统设计与论文撰写的同学参考。压缩包内共1个docx文件大小4.55MB内容包含摘要、目录、绪论、开发技术、系统分析等章节较完整地呈现了从需求分析到功能模块划分、角色权限管理管理员与用户以及首页信息推送等设计过程。目前已有499人学习下载。文档不仅梳理了Java与Spring Boot在项目中的实际应用还对宠物信息管理、领养申请、宠物论坛等核心功能进行了说明可帮助读者理解平台的整体架构与实现思路同时为毕业论文的结构编排和写作表述提供参照。1. 宠物领养救助平台这个 SpringBootVue 选题真正要交付什么把宠物领养救助平台做成毕业设计第一眼是标准的增删改查真正决定工作量的是贯穿全站的状态流转一只宠物从被救助人上传到审核通过展示再到提交领养申请、完成领养每一步都要改状态、留记录。SpringBoot 管接口和权限Vue 管列表、表单和状态提示两端的联调才是论文里值得写、答辩时值得讲的部分。这个选题适合两类人一类要快速搭出可运行系统、把精力留给论文撰写另一类想在基础功能上加亮点比如救助站地图、回访记录模块。核心路径一致先让管理员、救助人、领养申请人三种角色跑通提交流程。下面按做这类项目的顺序展开先讲业务边界和表结构再给 SpringBoot 后端的最小实现然后是 Vue 前端如何对接最后落在联调验证上。2. SpringBootVue 宠物领养救助平台的业务拆分与数据库表设计拿到需求先别急着建实体类和写接口。宠物领养救助平台表面上是一堆信息发布功能真正复杂的是业务状态我一般会先把角色、状态、流转路径画成表格再回头定表结构。以下拆法是做这类平台最常见、也最不容易返工的落库方式。2.1 领养、送养、救助三条业务线共用一张宠物表送养是用户把自己养的宠物挂出来救助是发现流浪动物后上报、由救助站接手领养是用户申请领养平台上的宠物。三条线最终都落到宠物身上所以核心表只有 pet_info用 source_type 区分宠物来自送养还是救助用 pet_status 控制当前处于哪个阶段。角色上只有三种权限差异体现在接口和页面可见性上。角色能做什么权限控制方式游客浏览公示中的宠物列表和详情只读接口无需 token普通用户发布送养、上报救助线索、提交领养申请、查看申请进度登录后用 user_id 过滤本人数据救助站 / 管理员审核宠物上架、审核领养申请、登记救助记录角色标记 admin接口单独校验这张表决定后续接口的前缀划分/api/pet是全站公开查询/api/apply需要登录/api/admin只对管理员开放。拦截器按路径前缀控制比在每个接口里写 if 判断省事得多论文里画权限图也更清晰。2.2 核心表的字段设计与状态取值约定整个平台只需要五张表就能撑起主流程剩下的是扩展功能表。字段命名统一用下划线风格MyBatis-Plus 开启驼峰映射后直接对应实体属性。表名职责关键字段与主表关系user账号、角色id, username, password, role无pet_info宠物档案pet_status, source_type, cover_img, owner_id无adoption_apply领养申请apply_status, pet_id, user_id, reasonpet_id 指向 pet_inforescue_record救助记录pet_id, reporter_id, rescue_status, addresspet_id 允许为空category宠物分类name, sortpet_info.category_idCREATE TABLE pet_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT, pet_name VARCHAR(50) NOT NULL COMMENT 宠物昵称, category_id BIGINT NOT NULL COMMENT 分类关联 category.id, cover_img VARCHAR(255) COMMENT 封面图相对路径如 /upload/xxx.jpg, pet_status TINYINT NOT NULL DEFAULT 0 COMMENT 0待审核 1公示中 2已被申请 3已完成领养 4下架, source_type TINYINT NOT NULL DEFAULT 1 COMMENT 1送养 2救助, owner_id BIGINT NOT NULL COMMENT 发布人或救助站用户 id, created_time DATETIME DEFAULT CURRENT_TIMESTAMP, updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE adoption_apply ( id BIGINT PRIMARY KEY AUTO_INCREMENT, pet_id BIGINT NOT NULL COMMENT 申请哪只宠物, user_id BIGINT NOT NULL COMMENT 哪个用户提交的, reason VARCHAR(500) COMMENT 领养理由, contact_phone VARCHAR(20) COMMENT 联系电话, apply_status TINYINT NOT NULL DEFAULT 0 COMMENT 0待审核 1已通过 2已拒绝 3已完成领养, created_time DATETIME DEFAULT CURRENT_TIMESTAMP );pet_status 和 apply_status 故意分开宠物维度的状态与申请维度的状态不同步更新。申请通过后才会把 pet_status 置为 3拒绝时宠物退回到 1 重新公示。cover_img 只存相对路径而不是完整 URL本地开发访问/upload/1.jpg部署后用同域名的 Nginx 指向同一目录不用改数据。提示password 字段在演示项目里也别存明文。用 BCrypt 加密存 60 位哈希答辩时被问到密码安全可以顺势讲清楚。2.3 用 MyBatis-Plus 写数据访问层少写多表 XML毕设场景里真正要手写 SQL 的多表查询很少列表、详情、状态更新都可以用 wrapper 完成。MyBatis-Plus 是教程量最大的选择分页插件引入后直接selectPage省掉手写 PageHelper 或 limit 的麻烦。Service public class PetService { Autowired private PetInfoMapper petInfoMapper; public IPagePetInfo pagePublished(int current, int size, Long categoryId) { LambdaQueryWrapperPetInfo wrapper new LambdaQueryWrapper(); // 只查公示中的宠物待审核和下架的都不出现在前台 wrapper.eq(PetInfo::getPetStatus, 1); // 分类条件为空时自动忽略不拼进 SQL wrapper.eq(categoryId ! null, PetInfo::getCategoryId, categoryId); wrapper.orderByDesc(PetInfo::getCreatedTime); return petInfoMapper.selectPage(new Page(current, size), wrapper); } }eq(first, second, third)的第一个参数是 boolean 条件为 false 时整条不参与拼接替代连续 if 判断。分页参数约定为 current 和 size返回对象里带 records、total 两个字段前端 el-pagination 直接读 total 渲染页码。排序用 created_time 倒序新发布的宠物排前面这个细节在答辩演示时容易被注意到。Mapper 接口只需继承BaseMapperPetInfo启动类上加MapperScan(com.example.mapper)即可不用写一行 XML。3. SpringBoot 后端接口登录拦截、图片上传与领养申请审核后端部分我习惯按三个接口组写公开查询、登录态操作、管理员审核。下面给出认证选型和三个最容易被追问的接口实现代码可以直接抄进项目改包名。3.1 认证选型Sa-Token 比 Spring Security 更适合毕设的理由Spring Security 在简历上是亮点但毕设周期内从零配过滤器链容易卡在登录闭环上。Sa-Token 的主要优势是登录即 token、注解即鉴权中文文档全出问题搜得到。对比项Spring SecuritySa-Token学习成本过滤器链、多种配置方式入门门槛高登录即 token注解即鉴权与 Vue 前端配合token 生成校验要自己写自带 token 管理与续期答辩可讲性讲过滤器链是亮点更多精力留给业务流转如果导师没有硬性要求我一般会用 Sa-Token。代码里SaCheckLogin、SaCheckRole(admin)一眼能看懂论文里也好写。后面所有接口示例都基于这个选型。3.2 登录注册接口与拦截器配置登录接口只做两件事校验账号密码调用StpUtil.login生成 token。前端后续请求带上 token后端拦截器统一放行。RestController RequestMapping(/api/user) public class UserController { Autowired private UserService userService; PostMapping(/login) public R login(RequestBody LoginDTO dto) { User user userService.login(dto.getUsername(), dto.getPassword()); // 登录成功StpUtil 生成并管理 token StpUtil.login(user.getId()); return R.ok(StpUtil.getTokenInfo()); } PostMapping(/register) public R register(RequestBody RegisterDTO dto) { userService.register(dto); return R.ok(); } }StpUtil.getTokenInfo()返回 tokenName 和 tokenValue前端保存后每次请求带在 header 里。userService.login内部做密码校验失败抛异常由全局异常处理器转成统一返回结构。业务接口里通过StpUtil.getLoginIdAsLong()拿当前用户 id前端传 user_id 一律不信任这个点答辩常问。Configuration public class SaTokenConfigure implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { // 拦截 /api/ 下所有请求白名单放行 registry.addInterceptor(new SaInterceptor(handle - StpUtil.checkLogin())) .addPathPatterns(/api/**) .excludePathPatterns( /api/user/login, /api/user/register, /api/pet/**, /api/upload/** ); } }addPathPatterns与excludePathPatterns定义白名单边界。/api/pet/**保持公开因为宠物列表和详情是游客主页的数据来源/api/upload/**放行是因为图片是公开资源。新增接口时要评估是否放进白名单漏放会导致前端 401。3.3 领养申请审核条件 UPDATE 防止重复领养这是整个平台最值得讲的接口先查重再用带状态的 UPDATE 抢占宠物最后插入申请记录。三步在一个事务里。Transactional public void submitApply(ApplyDTO dto, Long userId) { // 同一用户对同一宠物不能有未完结的申请 Long count adoptionApplyMapper.selectCount( new LambdaQueryWrapperAdoptionApply() .eq(AdoptionApply::getPetId, dto.getPetId()) .eq(AdoptionApply::getUserId, userId) .in(AdoptionApply::getApplyStatus, 0, 1)); if (count 0) { throw new BizException(已提交过申请请等待审核结果); } // 宠物从“公示中”改成“已被申请”条件带 pet_status1 int updated petInfoMapper.update(null, new LambdaUpdateWrapperPetInfo() .eq(PetInfo::getId, dto.getPetId()) .eq(PetInfo::getPetStatus, 1) .set(PetInfo::getPetStatus, 2)); if (updated 0) { throw new BizException(宠物当前状态不可申请); } AdoptionApply apply new AdoptionApply(); apply.setPetId(dto.getPetId()); apply.setUserId(userId); apply.setReason(dto.getReason()); apply.setContactPhone(dto.getContactPhone()); apply.setApplyStatus(0); adoptionApplyMapper.insert(apply); }核心是第二个条件更新where 条件带pet_status1用 update 返回的影响行数判断是否抢到。两个请求同时进来时数据库行锁保证只有一个能更新成功另一个 updated0 直接报错。先查再改的写法在并发下会放进来两条申请答辩时讲并发安全就讲这一段。Transactional保证 insert 失败时状态回滚回 1避免宠物卡死在“已被申请”。3.4 图片上传与静态资源映射宠物照片是平台的刚需。上传接口把文件写到本地磁盘返回相对路径数据库只存路径字符串。RestController RequestMapping(/api) public class UploadController { Value(${upload.dir}) private String uploadDir; PostMapping(/upload) public R upload(MultipartFile file) throws IOException { if (file.isEmpty()) { return R.fail(请选择文件); } String ext StringUtils.getFilenameExtension(file.getOriginalFilename()); String filename UUID.randomUUID().toString().replace(-, ) . ext; File dir new File(uploadDir); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(dir, filename)); return R.ok(/upload/ filename); } }文件名用 UUID 拼接原扩展名避免中文名和重名覆盖更严谨的做法是把扩展名放进白名单数组再校验。接口返回相对路径前端拼在接口域名后面就能访问。存放目录由upload.dir在 application.yml 里配置本地写./upload/服务器写/opt/pet-upload/切环境只改一处。Configuration public class WebConfig implements WebMvcConfigurer { Value(${upload.dir}) private String uploadDir; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // /upload/** 映射到磁盘 uploadDir registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadDir /); } }还需要在 application.yml 里放开上传大小限制默认 1MB 经常不够用spring: servlet: multipart: max-file-size: 5MB max-request-size: 10MB不放开的话上传超过 1MB 直接报MaxUploadSizeExceededException终端报错时先检查这里再找代码问题。4. Vue 前端工程Vite 初始化、路由守卫与领养申请数据流前端部分按“环境装好、请求封装、页面串流程”三步走。毕设场景下组件内 ref 加路由跳转就够不引入复杂状态管理减少出问题的面。4.1 用 Vite 初始化 Vue3 项目并安装依赖vue 安装及环境配置最常见的坑是 node 版本和包管理工具不一致。Vite 要求 Node 18 以上环境确认没问题后再执行npm create vitelatest pet-front -- --template vue cd pet-front npm install npm install axios vue-router4 npm run dev前三步是 vue 安装依赖的标准流程vue-router 单独装是因为模板默认不带路由。dev server 默认跑在 5173 端口打开 http://localhost:5173 看到示例页说明环境正常。接着改 vite.config.js 配置接口转发import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { // 开发环境把 /api 开头的请求转发到后端 8080 /api: { target: http://localhost:8080, changeOrigin: true } } } })这个配置让前端开发时直接用/api/xxx的相对路径请求浏览器地址栏始终是 5173规避跨域。生产环境不依赖这层转发由 Nginx 在同一个 server 里做同样的请求转发所以接口路径风格前后保持一致。4.2 Axios 封装token 携带与统一错误拦截每个页面单独写 axios 请求会散落一堆重复代码我一般会在 src/api 下抽一个 request.jsimport axios from axios import { ElMessage } from element-plus import router from /router const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器自动携带登录 token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[satoken] token } return config }) // 响应拦截器统一解包和错误提示 request.interceptors.response.use( res { const data res.data if (data.code ! 200) { ElMessage.error(data.msg) return Promise.reject(new Error(data.msg)) } return data }, err { if (err.response err.response.status 401) { localStorage.removeItem(token) router.push(/login) } return Promise.reject(err) } ) export default requestbaseURL 写/api所有请求自动带前缀请求拦截器从 localStorage 取 token 放进 headerheader 名字要和后端 Sa-Token 配置一致默认是 satoken不一致会一直 401响应拦截器统一解包code 非 200 时弹出后端返回的 msg业务代码里只处理成功分支。401 由后端登录失效返回在这里统一清 token 跳登录页避免每个页面重复写。后端统一返回结构需要前后端一起定保持一致字段类型说明codeint200 成功401 未登录500 业务失败msgstring给前端展示的中文提示dataobject业务数据分页时包含 records、total、current、size4.3 vue 路由与登录守卫路由配置里用meta.requiresAuth标记需要登录的页面路由守卫统一判断const routes [ { path: /, component: Home, name: home }, { path: /pet/:id, component: PetDetail, name: petDetail }, { path: /apply, component: ApplyForm, name: apply, meta: { requiresAuth: true } } ] router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next(/login) } else { next() } })meta 里挂 requiresAuth 标登录态beforeEach 检查本地 token 决定放行或跳转。这只解决页面可见性接口安全要靠后端 StpUtil.checkLogin。vue 路由参数在 4.4 里会用到/pet/:id这种动态段在组件里通过route.params.id读取。4.4 宠物详情页与领养申请表单的数据流详情页在 onMounted 里按路由参数拉数据提交申请只负责调接口成功才跳转失败提示由拦截器统一处理script setup import { ref, onMounted } from vue import { useRoute, useRouter } from vue-router import { getPetDetail, submitApply } from /api/pet const route useRoute() const router useRouter() const pet ref(null) const onSubmit async (reason, phone) { try { // 只传业务参数user_id 由后端从 token 里取 await submitApply({ petId: pet.value.id, reason, phone }) ElMessage.success(申请已提交) router.push(/my/apply) } catch (e) { // 错误已由拦截器提示这里不需要再弹窗 } } onMounted(async () { pet.value await getPetDetail(route.params.id) }) /script这个组件体现了前后端分工页面只关心“展示什么、提交什么”不关心“谁在提交、能不能提交”。pet 状态为“公示中”时才显示申请按钮后端再做一次状态校验双重判断保证体验和数据的正确性。申请成功后跳转到“我的申请”列表那边是纯列表渲染接分页接口即可。5. 联调验证Nginx 合流、边界自检与答辩追问5.1 用 Nginx 把前端构建产物与后端接口合到同一入口前端npm run build后 dist 目录就是上线产物。Nginx 配置三个 location 分别处理页面、接口和图片server { listen 80; server_name localhost; location / { root /opt/pet-front/dist; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; } location /upload/ { alias /opt/pet-upload/; } }try_files 是 Vue history 路由模式的关键否则刷新/pet/3这种二级页面会 404/api开头转发到后端 SpringBoot 的 8080/upload直接指向磁盘目录图片访问不走 SpringBoot减少一次应用层开销。改完配置用nginx -t检查语法再 reload。5.2 答辩必查的三个边界问题第一pet_status 和 apply_status 为什么要分成两个字段。一个宠物可能有多条申请记录宠物状态是“被申请中”申请状态各管各的混在一个字段里语义就乱了。第二重复领养怎么防。讲查重加条件 UPDATE重点说两个请求同时到达时数据库行锁保证只有一个更新成功。第三图片为什么存磁盘不存数据库。数据库只存路径文件放磁盘静态资源由 Nginx 直接返回数据库体积小、备份快。5.3 上线前的验证清单按这个顺序过一遍白名单接口无需登录能访问登录后携带 token 请求成功无 token 访问/api/apply返回 401同一用户对同一宠物连续提交两次申请第二次被拦截宠物被申请后列表不再显示“申请”按钮上传的图片刷新页面仍能访问。最后用无痕窗口按“注册—登录—发布—审核—申请—完成领养”完整走一遍接口耗时在 Network 面板里能直接看到前端白屏先看 console 再看 Network 响应码后端没响应先看 SpringBoot 控制台有没有堆栈这两步能定位大部分联调问题。本文还有配套的精品资源点击获取
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻