
做 Java 后端这块时间久了后台私信里出现频率最高的一类需求就是能直接跑、结构完整、还能拿去答辩的项目。搜来搜去宠物领养系统算是这类选题里出现频率很高的一个——业务模型清晰有用户和管理员两种角色有宠物管理、领养申请、审核流转这些不算复杂但逻辑完整的功能点特别适合拿来练手或者作为课程设计、毕业设计。我手里正好有一套基于 JavaSpring Boot加 Vue 的宠物领养系统项目包源码、数据库脚本、项目文档都齐全趁着整理资料的机会把这套系统从表设计到部署跑通的细节完整拆一遍。这套系统能做什么用户端可以注册登录、浏览筛选宠物、查看详情、提交领养申请、查询申请进度管理端可以发布宠物、管理上下架、处理领养申请并填写审核意见、管理用户和公告。如果再算上收藏、分类筛选这些功能业务闭环是很完整的。不管你是想照着做一个毕业设计还是想找个真实的前后端分离项目入门这篇都值得看完——我会把为什么这么设计和实际跑起来会踩什么坑一并讲清楚。1. 业务先于代码宠物领养系统的角色划分与功能全景1.1 线下领养场景的痛点决定了系统设计先别急着写代码。宠物领养系统的业务本质上可以抽象成一句话一方发布宠物信息另一方提交领养意愿中间需要一个管理方来做审核和撮合。这个抽象决定了系统必须有三种核心能力信息展示、申请提交、审核决策。线下场景里这三个动作全靠人工问题就出在透明度和时效性上。线上化之后用户能实时看到某只宠物是否还在待领养状态管理员不用再靠私聊群发来同步信息每个申请的状态、审核意见、时间线都有记录可查。所以做这类系统第一件事不是画页面而是把角色—动作—状态的三角关系理清楚。1.2 用户端功能清单用户端主要面向普通访客和注册用户功能可以按浏览期、申请期、等待期三个阶段来梳理游客浏览首页轮播公告、宠物列表、宠物详情可按分类和关键词筛选用户注册与登录用户名加密码注册登录后获得 token领养申请选择宠物、填写申请理由、提交后生成一条待审核记录申请进度查看查看自己的申请是待审核、已通过还是已拒绝看到管理员填写的审核意见个人中心头像、昵称、联系方式维护查看自己提交的申请记录收藏功能收藏感兴趣的宠物方便后续再对比这里有一个很关键的设计点用户提交领养申请之后系统应该记住这个用户和宠物之间的所有交互记录而不是只存一个孤零零的状态值。这样做的好处是审核阶段管理员能看到完整的时间线什么时候申请的、之前是否申请过其他宠物、是否被拒绝过以及原因。这也是为什么申请记录表需要独立于宠物表存在而不是在宠物表上加一个申请人字段就完事。1.3 管理端功能清单管理端是整套系统的中控台角色的职责决定了它的功能比用户端更像一个后台操作系统登录鉴权管理员账号和用户账号共用一张用户表用 role 字段区分宠物管理新增宠物含图片上传、编辑、上下架、删除发布时填品种、性别、年龄、健康状况、疫苗驱虫情况等领养申请审核查看申请列表、查看申请人信息与申请理由、通过或拒绝、填写审核意见用户管理查看注册用户列表、禁用或恢复用户账号公告管理发布、编辑、删除首页轮播公告分类管理维护宠物分类猫、狗、兔子、其他等管理端看起来很杂但拆解下来你会发现每个模块本质上都是列表查询 表单提交 状态修改三种基本操作的组合。真正考验设计水平的是领养申请审核这个模块——它联动了两张表的状态需要格外小心。1.4 核心业务流程一条主线把两端串起来把用户端和管理端串起来整套系统的主线业务就是宠物发布—用户申请—管理员审核—状态闭环管理员发布宠物宠物状态为待领养。用户在宠物详情页提交领养申请填写申请理由。系统生成一条申请记录状态为待审核此时该宠物可以被多个用户同时申请由管理员来裁决。管理员进入审核列表查看申请人信息和理由选择通过或拒绝并填写审核意见。如果通过宠物状态改为已领养申请状态改为已通过该宠物不可再被申请如果拒绝宠物保持待领养申请状态改为已拒绝。用户在我的申请里看到最终结果和审核意见。这个流程里最容易出错的地方在第 4 步和第 5 步——审核通过时必须用数据库事务同时更新申请状态和宠物状态否则会出现申请通过了但宠物还是待领养或者反过来宠物显示已领养但申请还是待审核的数据不一致。这个我在后面核心业务实现部分会专门展开。2. 技术选型为什么是 Java Vue前后端分离的取舍与理由2.1 后端选择 Spring Boot 的三个理由如果说项目是一个人的话后端就是这个人的逻辑和手。选 Spring Boot 而不是更老牌的 SSMSpring Spring MVC MyBatis核心原因是三点。第一零配置启动。SSM 时代光搭一个 Spring 容器就要写一堆 XMLSpring Boot 用自动配置把这一堆都吞掉了一个 SpringBootApplication 注解加一个 application.yml 就能把项目跑起来对新手极其友好出错的概率也低很多。第二生态太成熟。MyBatis-Plus 这种增强框架一接单表 CRUD 几乎不用写 SQLHutool 提供了日期、文件、加密、验证码等一堆工具方法JWT 认证有现成封装。做课程设计这个量级的项目基本不用重复造轮子。第三部署成本低。Spring Boot 自带内嵌 Tomcatmvn package 打出一个 jar 包java -jar 就能跑不用再去服务器上单独装 Tomcat 配虚拟目录。放到答辩演示场景里一个 jar 加一个前端静态包几分钟就能把整套环境起起来。2.2 前端选择 Vue 而不是 JSP 的关键差异这个选择对前后端分离这个命题特别关键。如果你用的是 JSP那前端页面和后端处理逻辑是揉在一起的改一个页面的样式可能牵扯到后端渲染逻辑典型的强耦合。Vue 把整个前端做成一个独立的静态工程通过 Axios 发 HTTP 请求调后端接口拿 JSON 数据渲染页面。这样做的好处有三个一是前后端可以完全并行开发后端只管出接口前端只管调接口二是页面交互体验好局部刷新天然支持不用整页跳转三是部署灵活前端打包成静态文件可以扔到 Nginx 上后端 jar 跑在任意端口互不干扰。当前市面上很多项目版本用 Vue 2 Element UI 的组合也有升级到 Vue 3 Element Plus 的版本。如果你自己从零搭个人建议直接上 Vue 3如果你拿到的源码是 Vue 2 的不用着急重写Vue 2 跑课程设计完全够用而且组件库资料多遇到问题好搜。2.3 配套组件的选择逻辑除了 Spring Boot 和 Vue这套系统通常还配套下面这些组件组件用途选型理由MySQL 5.7/8.0业务数据存储免费、稳定、教学和求职场景认可度高MyBatis-Plus数据持久层单表 CRUD 不用写 SQL自带分页插件Maven依赖管理与构建项目标配一拉依赖全部到位Node.js npm前端依赖与构建Vue 工程的运行基础JWTHutool 封装用户认证无状态认证前后端分离场景最合适AxiosHTTP 请求Promise 风格拦截器做 token 注入很方便这里顺便说一个常见问题为什么不用 Redis宠物领养系统的数据量级一个 MySQL 单库就完全扛得住引入 Redis 缓存属于过度设计——除非你答辩想专门讲高性能优化否则没必要给自己增加部署复杂度。同理也不用引入 Spring Security 全家桶一个拦截器校验 JWT 足够应付这个项目的权限控制需求。技术选型的原则从来不是越多越好而是够用且能讲清楚。2.4 拿到源码先看目录结构一套规范的源码前端和后端通常是两个独立目录。拿到项目包以后第一件事就是核对目录结构是否完整pet-adoption/ ├── backend/ # Spring Boot 后端 │ ├── src/main/java/com/pet/ │ │ ├── controller/ # 控制器层 │ │ ├── service/ # 业务逻辑层 │ │ ├── mapper/ # MyBatis 接口层 │ │ ├── entity/ # 实体类 │ │ ├── config/ # 配置类跨域、拦截器、分页插件 │ │ ├── common/ #返回体封装、异常处理、工具类 │ │ └── PetApplication.java │ ├── src/main/resources/ │ │ ├── application.yml │ │ └── mapper/ # XML 映射文件如需 │ └── pom.xml ├── frontend/ # Vue 前端 │ ├── src/ │ │ ├── api/ # Axios 接口封装 │ │ ├── views/ # 页面组件 │ │ ├── router/ # 路由配置 │ │ ├── store/ # 全局状态管理 │ │ ├── components/ # 公共组件 │ │ └── utils/ # 请求工具封装 │ ├── vue.config.js # 编译器与开发服务器配置 │ └── package.json ├── sql/ │ └── pet_adoption.sql # 建库建表 初始化数据 └── 文档/ └── 需求文档/设计文档/答辩材料缺少 controller/service/mapper 任何一层工程的规范程度就要打问号缺少 sql 目录意味着初始化数据要自己造工作量会大很多。3. 数据库设计拆解七张表如何支撑整套业务3.1 核心三表用户表、宠物表、领养申请表数据库是整个系统的地基。这个系统里真正核心的业务表有三张用户表user、宠物表pet、领养申请表adoption。剩下的辅助表是分类表category、公告表notice、收藏表favorite。先看用户表。这里有一个设计惯例管理员和普通用户不拆表用 role 字段区分0 表示普通用户1 表示管理员。这样做的好处是登录接口只需要写一套个人中心和管理端可以共用一个用户实体只是在前端菜单和接口权限上做区分。CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录名, password varchar(100) NOT NULL COMMENT 密码加盐哈希, nickname varchar(50) DEFAULT NULL COMMENT 昵称, phone varchar(20) DEFAULT NULL COMMENT 手机号, avatar varchar(255) DEFAULT NULL COMMENT 头像地址, role tinyint(1) NOT NULL DEFAULT 0 COMMENT 0-用户 1-管理员, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 1-正常 0-禁用, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;这里我强烈建议不要用明文密码存库。课程设计项目最常见的扣分项之一就是管理员把密码直接写成 admin123 存进去。用 Hutool 的 DigestUtil 做一次加盐哈希或者接 Spring Security 的 BCrypt虽然谈不上多高级但至少能跟答辩老师讲清楚我没有明文存储密码。再看宠物表。宠物字段要覆盖用户详情页最关心的信息以及管理员审核时可能需要的信息CREATE TABLE pet ( id int(11) NOT NULL AUTO_INCREMENT, category_id int(11) NOT NULL COMMENT 分类id, name varchar(100) NOT NULL COMMENT 宠物名称, gender tinyint(1) DEFAULT 0 COMMENT 0-未知 1-公 2-母, age varchar(20) DEFAULT NULL COMMENT 月龄/年龄描述, breed varchar(50) DEFAULT NULL COMMENT 品种, health_status varchar(255) DEFAULT NULL COMMENT 健康状况描述, vaccine tinyint(1) DEFAULT 0 COMMENT 是否已驱虫疫苗 0-否 1-是, description text COMMENT 详细描述, image varchar(255) DEFAULT NULL COMMENT 宠物图片地址, status tinyint(1) NOT NULL DEFAULT 0 COMMENT 0-待领养 1-已领养 2-下架, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT宠物表;age 字段我用字符串而不是 int因为宠物年龄往往需要表达2个月大约3岁这种模糊描述写成字符串更容易展示也避免在数值上过度纠结。这种细节不一定需要专门跟人讲但它体现了你能根据实际业务调整字段类型是设计能力的加分项。领养申请表是业务核心中的核心CREATE TABLE adoption ( id int(11) NOT NULL AUTO_INCREMENT, user_id int(11) NOT NULL COMMENT 申请人id, pet_id int(11) NOT NULL COMMENT 宠物id, reason varchar(500) DEFAULT NULL COMMENT 申请理由, status tinyint(1) NOT NULL DEFAULT 0 COMMENT 0-待审核 1-已通过 2-已拒绝 3-已完成, review_remark varchar(500) DEFAULT NULL COMMENT 审核意见, apply_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 申请时间, review_time datetime DEFAULT NULL COMMENT 审核时间, PRIMARY KEY (id), UNIQUE KEY uk_user_pet (user_id, pet_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT领养申请表;uk_user_pet 这个联合唯一索引很容易被忽略但它直接解决了一类业务 Bug同一用户对同一只宠物重复提交申请。没有这个约束用户狂点提交按钮就能生成一堆垃圾记录后期数据根本没法看。当然更严谨的做法是接口层再判断一次返回您已申请过该宠物的友好提示数据库约束兜底两层都做。3.2 辅助表与字段设计细节分类表很简单就是 id、name、sort、create_time 几个字段用于前端下拉框和宠物列表筛选。公告表存 id、title、content、image、create_time管理端发布后用户端首页轮播展示。收藏表存 id、user_id、pet_id、create_time联合唯一索引同样加上防止重复收藏。字段规范层面有个建议所有表都加上 create_time 和 update_time并且宠物主表用状态字段代替物理删除。为什么要这样因为答辩老师大概率会问删除宠物是物理删除还是逻辑删除。如果你在 pet 表里设计了 status2下架而不是真的 DELETE你可以顺势讲出一个设计思路用下架代替删除保留历史领养记录和用户申请记录方便日后追溯。这个回答比我用了物理删除要漂亮得多。3.3 外键为什么能不用就不用很多新手喜欢在表之间加外键约束觉得这样数据一致性强。实际工程中外键约束会带来几个麻烦第一级联删除、更新逻辑容易被业务绕过还会造成锁竞争第二分库分表之后外键完全失效第三批量导入数据时外键校验拖慢速度。所以这套系统的表之间我只在字段层维护逻辑关系不建物理外键业务的一致性由 service 层事务来保证。这种设计的唯一要求是查询的时候用 JOIN 或 MyBatis-Plus 的关联查询把申请人昵称、宠物名称等展示字段拼好。我通常在 VO 对象里直接冗余这些字段前端拿到的 JSON 里就有 nickname、petName、petImage不需要再拿一个 id 去另一个接口查。前端省一次请求后端少写一个接口大家都轻松。3.4 初始化数据演示效果成败的关键数据库脚本里除了建表一定要包含初始化数据。我见过太多项目 SQL 文件只有表结构没有数据前端跑起来列表空空一片连截图都没法截。初始化数据至少要包含一个管理员账号admin、一个演示用户、5 到 8 只覆盖不同分类和状态的宠物、几条公告、一两条带审核状态的申请数据。为什么要带审核记录因为你要演示申请状态从待审核变成已通过这个变化过程如果没有一条已通过的记录答辩演示时就得当场完整操作一遍流程万一现场网络卡顿或者哪一步操作失误就很被动。有一条现成的已通过记录你可以直接打开列表展示再现场操作新的申请流程两条线同时演示效率高得多。4. 核心业务实现领养状态机、接口设计、认证与上传4.1 领养申请的状态机与事务联动领养申请的状态流转是整套系统最值得讲的逻辑。建议直接用 int 状态字段配合常量类管理定义如下0待审核PENDING1已通过APPROVED2已拒绝REJECTED3已完成FINISHED其中已完成代表线下交接结束管理员在审核通过后还可以继续做一次确认领养完成操作让数据闭环更完整。如果不想做这么细也可以把 1 和 3 合并成一个已领养但答辩时状态设计过于简单可能会被追问。状态流转要注意几条约束只有待审核状态才能变更为已通过或已拒绝已通过的申请会联动更新 pet 表状态为已领养而且这个动作必须在同一个事务里完成。用 Spring 的事务注解实现联动非常直观Transactional(rollbackFor Exception.class) public void auditAdoption(Integer adoptionId, Integer auditStatus, String reviewRemark) { // 1. 校验申请记录存在且为待审核 Adoption adoption adoptionMapper.selectById(adoptionId); if (adoption null || adoption.getStatus() ! 0) { throw new BusinessException(申请记录不存在或已被处理); } // 2. 更新申请状态 adoption.setStatus(auditStatus); adoption.setReviewRemark(reviewRemark); adoption.setReviewTime(new Date()); adoptionMapper.updateById(adoption); // 3. 审核通过时同步更新宠物状态 if (auditStatus 1) { Pet pet petMapper.selectById(adoption.getPetId()); pet.setStatus(1); // 已领养 petMapper.updateById(pet); } }这段代码虽然短但它包含了两个容易被忽略的细节一是先做状态校验防止重复审核二是用 Transactional 保证申请状态 宠物状态的一致性。这两点都可以作为答辩时的谈资。4.2 前后端接口设计RESTful 风格与统一返回体接口设计我采用 RESTful 风格统一返回体封装为 Result 对象code 200 表示成功500 表示业务或系统异常前端根据 code 做统一提示。核心接口给出一份清单方便你对照源码检查是否有遗漏模块接口方法说明用户/api/user/loginPOST登录返回 token用户/api/user/registerPOST注册宠物/api/pet/pageGET分页列表支持分类/关键词筛选宠物/api/pet/detail/{id}GET宠物详情收藏/api/favorite/addPOST收藏领养/api/adoption/applyPOST提交申请领养/api/adoption/myListGET我的申请列表管理/api/admin/pet/savePOST新增/编辑宠物管理/api/admin/adoption/listGET审核列表管理/api/admin/adoption/auditPOST审核申请管理/api/admin/user/updateStatusPOST禁用/恢复用户分页接口用 MyBatis-Plus 的 Page 插件实现会比较省事GetMapping(/api/pet/page) public ResultPagePetVO page(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) Integer categoryId, RequestParam(required false) String keyword) { PagePet page new Page(pageNum, pageSize); LambdaQueryWrapperPet wrapper new LambdaQueryWrapper(); if (categoryId ! null) { wrapper.eq(Pet::getCategoryId, categoryId); } if (StringUtils.hasText(keyword)) { wrapper.like(Pet::getName, keyword).or().like(Pet::getBreed, keyword); } wrapper.eq(Pet::getStatus, 0); // 只展示待领养宠物 wrapper.orderByDesc(Pet::getCreateTime); return Result.ok(petMapper.selectPage(page, wrapper)); }注意这里只查 status0 的宠物避免用户端看到已领养和下架的数据。keyword 的模糊查询同时匹配 name 和 breed用 like 加上 or 的组合就行课程设计级别不用上全文索引。4.3 登录认证JWT 加拦截器的轻量方案前后端分离项目最流行的认证方案就是 JWT。流程很简单用户登录成功后后端用密钥生成一个 token 返回前端把 token 存到 localStorageAxios 拦截器在每次请求头里带上 AuthorizationBearer 加 token后端写一个拦截器拦截除登录、注册、宠物列表以外的 /api/** 请求校验 token 是否有效。Hutool 的 JWTUtil 做这件事非常省事// 生成 token String token JWTUtil.createToken( Map.of(userId, user.getId(), role, user.getRole().toString()), secretKey.getBytes() ); // 校验 token JWTValidator.of(token).validateDate();拦截器里注意两点一是放行路径要配全登录、注册、公开的宠物列表等二是从 token 里解析出 userId 后要放到 ThreadLocal 或请求属性里方便后续接口取当前登录用户。需要管理员权限的接口在拦截器里再做一次角色判断即可课程设计级别不用搞复杂的权限框架。密码存储再强调一次如果源码里是明文密码一定要改成加盐哈希。至少做到用用户名作为盐MD5 拼接后再哈希这样就算数据库泄露常见彩虹表也不能直接匹配。答辩时主动提一句所有密码经过加盐哈希存储是明确的加分项。4.4 图片上传与文件存储策略宠物图片是这个系统的门面。图片处理常见做法有三种本地上传保存到项目运行目录、存对象存储、转 base64 存数据库。前两种推荐第三种强烈不推荐——base64 会把图片体积膨胀约 33%存几张图数据库就吃不消而且每次列表查询都要把大字段序列化性能很差。课程设计项目建议用本地上传加静态资源映射方案。后端接收 MultipartFile保存到磁盘上的 upload 目录然后通过配置把 /upload/** 访问路径映射到那个目录Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String uploadPath System.getProperty(user.dir) /upload/; registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath); } }保存文件时用 UUID 重命名避免中文文件名和重名覆盖问题。前端展示时直接用 http://localhost:8081/upload/xxx.jpg 访问即可。如果答辩要在别的电脑上演示注意 upload 目录的绝对路径可能不同建议把上传路径配置在 application.yml 里方便现场改。4.5 表单校验与重复申请的边界处理最后说两个看起来小、实际经常出问题的点。第一是表单校验。后端接收注册、申请等参数时一定要做基础校验用户名非空、密码长度、手机号格式、申请理由长度。可以用 Spring Boot 的 Valid 注解加自定义异常处理也可以手写判断统一抛 BusinessException。没有校验的接口前端随便传一个空字符串数据库里就会多一条垃圾数据后面查问题非常痛苦。第二是重复提交。除了数据库唯一索引兜底接口层也要判断一次。以提交领养申请为例Long count adoptionMapper.selectCount( new LambdaQueryWrapperAdoption() .eq(Adoption::getUserId, currentUserId) .eq(Adoption::getPetId, petId) .in(Adoption::getStatus, 0, 1) // 待审核或已通过都视为已申请 ); if (count 0) { throw new BusinessException(您已申请过该宠物请勿重复提交); }这里为什么把待审核或已通过作为重复判断条件因为已拒绝的记录可以允许用户再次申请这是一种更合理的业务规则。如果你觉得逻辑太多也可以统一判定只要存在记录就拒绝按自己的业务取舍来。关键是要有这层判断而不是裸奔着让数据库去兜底。5. 环境搭建与部署避坑从零跑通项目的完整记录5.1 环境准备清单与版本搭配拿到源码第一步是准备环境。版本搭配整理成一张表照着配基本不会出问题环境推荐版本说明JDK1.8 或 11Spring Boot 2.x 标配 1.8部分新版依赖要求 11Maven3.63.8 以上注意仓库镜像配置MySQL5.7 或 8.05.7 入门最稳8.0 注意驱动名和时区Node.js14/16Vue2、18Vue3Vue2 工程用 Node 16 最稳20 以上可能遇到依赖兼容问题IDEIDEA 2021后端必备前端可用 VSCode版本踩坑其实是最多的。Spring Boot 2.7 配 MySQL 8.0如果用老的 com.mysql.jdbc.Driver 驱动启动直接报 ClassNotFoundException换成 com.mysql.cj.jdbc.Driver还要在 JDBC URL 后加上 serverTimezoneAsia/Shanghai。再比如 Node 20 装某些老版本 sass-loader编译直接报 Python 相关错误很多人卡在这一步。这些问题的解决方式都是把相关组件版本对齐。5.2 数据库导入的正确姿势SQL 脚本导入最常见的问题是编码。很多 SQL 文件本身是 UTF-8但 MySQL 客户端默认按 latin1 解析导入后中文全部变成问号。正确做法是先用文本编辑器打开 SQL 文件确认开头有 SET NAMES utf8mb4 或建表语句中带 CHARSETutf8mb4再用命令行导入时把字符集显式指定为 UTF-8。推荐命令行导入最不容易出错mysql -uroot -p123456 --default-character-setutf8mb4 pet_adoption.sql导入后不要急着启动先跑几个查询确认数据在不在SELECT COUNT(*) FROM pet; SELECT username, role FROM user;很多源码的 SQL 文件名带版本号或日期选最新的那个导入即可。如果导入中间报错中断先 DROP DATABASE 再重新导入不要尝试接着上次的进度继续容易留下半张表。5.3 前后端联调跨域、端口、代理三板斧前后端分离项目跑起来最常遇到的障碍是跨域。后端接口跑在 8081前端页面跑在 8080浏览器会拦截跨域请求。解决方式通常有两种。第一种是后端开启跨域配置在 WebConfig 里加 CorsFilter。第二种更推荐前端用 devServer 的 proxy 代理把 /api 开头的请求转发到后端端口。这样浏览器只和 8080 打交道不存在跨域问题同时后端也不用对所有来源放开限制安全性更好// vue.config.js module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:8081, changeOrigin: true } } } }配置完一定要记得重启前端 devServer很多人改了 vue.config.js 不重启还以为是代理失效。端口方面如果 8080 被占用改 devServer.port后端端口在 application.yml 的 server.port。两个端口不要设成一样否则请求自己打自己。5.4 一套跑通流程与排查思路我自己实测过的最稳启动顺序是启动 MySQL导入 SQL确认表和初始化数据都在。后端 IDEA 打开 backend 目录等 Maven 依赖下载完检查 application.yml 里的数据库账号密码是否匹配运行 PetApplication 主类。看到 Tomcat started on port 8081 后浏览器访问 http://localhost:8081/api/pet/page?pageNum1pageSize1如果返回 JSON 数据说明后端正常。前端命令行进入 frontend 目录先 npm install再 npm run serve。浏览器打开 http://localhost:8080注册一个用户走一遍浏览宠物—收藏—申请领养—查看进度流程。用管理员账号登录走一遍宠物发布—申请审核—通过或拒绝流程。前后端联调报错时先按这个思路排查打开浏览器 F12 Network 面板看请求到底有没有发出、状态码是多少。504/502 大概率是代理没配好401 说明 token 没带上或已过期500 就切到后端控制台看具体异常堆栈。思路对了问题解决速度至少快一倍。5.5 常见报错速查表把课程设计项目里出现频率最高的报错整理成表每一条都是实战中踩过的报错信息原因解决方案ClassNotFoundException: com.mysql.cj.jdbc.DriverMySQL 驱动版本不对pom 里改用 mysql-connector-java 8.0.xUnknown database pet_adoption库没建或名字不对先建库再导表核对 URL 中的库名Access denied for user root数据库密码错误修改 application.yml 中的密码Invalid bound statement (not found)Mapper XML 没扫描到加 MapperScan核对 resources 下 XML 路径Cannot resolve symbol Result统一返回类不在当前包检查 common 包是否引入IDEA 重新导入 Mavennpm ERR! ERESOLVE unable to resolve dependency tree依赖版本冲突尝试 npm install --legacy-peer-depsNo matching version found for sassNode 版本过高换 Node 16 或改 dart-sass前端 F12 显示跨域拦截代理没配或没重启配置 vue.config.js proxy 并重启 devServerElement UI 图标显示方块字体文件加载失败确认 public 目录静态资源完整重新 npm install这张表不可能覆盖所有问题但课程设计级别 80% 的问题就这几类。遇到新报错先看后端控制台异常堆栈的第一行再拿去搜绝大多数都能解决。6. 源码、数据库、文档交付一份能打的项目包6.1 源码结构规范让老师一眼看到你会分层源码拿到手之后不要直接交先做一次自查controller 层是否只做参数接收和结果返回业务逻辑是否都在 service 层mapper 是否只做数据访问。三层分离清晰的代码哪怕功能少一点答辩印象分也会高不少。一个常见的反面案例把所有业务逻辑写在 controller 方法里一个方法七八十行数据库操作直接在 controller 里写。这种代码答辩老师多问两句就露馅了。正向的例子是controller 接收参数,调用 service 的业务方法service 里做校验、状态判断、调 mappermapper 查库返回。每个方法职责单一从方法名就能看出它想干什么。6.2 数据库文档怎么整理才有说服力项目文档包里除了需求说明和操作手册数据库设计文档是硬指标。整理方式很简单对每张表做一张结构说明表把列名、类型、约束、注释都列清楚再画一张表关系图Word 或 PPT 里用表格和箭头即可不必专门学建模工具。文档里最好解释两个设计决策为什么用户表不拆管理员为什么领养表加联合唯一索引这两个问题能回答清楚文档就不只是流水账而是真的包含设计思考。6.3 答辩高频问题与加分回答思路答辩环节基本围绕为什么这么设计和某某功能怎么实现的展开。高频问题整理如下每个都附回答要点问前后端如何交互答前端 Vue 通过 Axios 调后端 RESTful 接口返回统一 Result JSON开发时用 devServer 代理解决跨域。问用户权限怎么控制答登录返回 JWT前端存储并注入请求头后端拦截器校验 token 并识别角色管理员接口额外校验。问领养审核为什么用事务答审核通过时需要同时修改申请表和宠物表的状态事务保证两个操作要么都成功、要么都回滚避免数据不一致。问图片怎么存的答存本地磁盘UUID 重命名通过静态资源映射访问数据库只存 URL 字符串避免数据库膨胀。问用户重复点击申请按钮怎么办答两层防护数据库联合唯一索引兜底service 层再判断一次返回友好提示。这些问题的回答思路都是前面讲过的设计点。真正理解这些设计比背一百道面试题管用。6.4 二次开发的三个方向如果项目做完还有余力或者想让项目看起来比平均水平高一点可以从下面三个方向里挑一个做扩展申请进度通知。审核通过或拒绝时给用户发送站内消息用户在个人中心查看消息列表。需要加一张 notification 表代码量不大但能体现你对用户体验有考虑。宠物多图展示。把 pet 表单图扩展成 pet_image 子表详情页做成轮播图。这个改动直接提升项目的视觉完成度。数据统计仪表盘。管理端首页做统计展示宠物总数、领养成功率、各分类占比用 ECharts 画柱状图或饼图。这个方向很受答辩老师欢迎因为一提到数据统计项目的层次就不一样了。我个人经验是从这三个方向里选一个做比把现有功能再堆三个 CRUD 模块要值得多——前者体现的是增量设计思考后者只是重复劳动。宠物领养系统这个项目能流行这么多年不是没有理由的。它正好卡在一个甜点位业务模型不复杂但又不只是单纯的增删改查技术栈是主流 Java 加 Vue前后端分离、JWT 认证、状态流转、事务处理全都覆盖到了。如果你正在做这个题目我最后再唠叨一句不要只满足于把代码跑通把每个设计决策背后的为什么想明白这比源码本身值钱。毕竟面试官和答辩老师想看到的从来都是那个能讲清楚来龙去脉的人而不是复读机。