简介:《SpringBoot+Vue宠物领养系统答辩PPT》是一份计算机毕业设计答辩演示文档,面向采用Spring Boot与Vue.js技术栈开展宠物领养管理类课题的本科生,用于展现项目的选题价值、开发思路和最终成果。内容按项目推进顺序展开:先说明宠物领养信息处理的选题背景和现实意义,再介绍以Java、Spring Boot、MySQL、Vue.js为基础的开发环境,以及基于B/S架构和UML建模的系统设计方式;随后给出管理员和用户两个角色的功能结构——管理员可管理用户,发布宠物领养、认领、教学视频、感谢信和公告,并对领养、认领申请进行审核,用户可申请领养或认领宠物、查看审核状态、发布感谢信、收藏教学视频或留言评论。PPT中还提供了从登录验证到分角色操作的系统流程,并总结开发过程中遇到的问题与解决方案,能够帮助答辩者快速梳理汇报主线,突出系统角色权限划分和业务闭环。资源共包含1个pptx演示文稿文件,压缩包大小仅为2.65MB,文件类型单一,打开即可浏览。目前已有1482人浏览学习,适合作为SpringBoot与Vue前后端分离项目毕业设计PPT的参考模板,也可用于整理自身项目的模块设计、功能流程和答辩要点。
1. 宠物领养系统答辩PPT:一份演示文稿背后要撑住的三件事
「SpringBoot+Vue宠物领养系统答辩PPT」这个名字看起来只是一份演示文稿,实际上它代表着一整套毕业设计答辩的技术方案。评委在看这份PPT时,真正关心的不是页面好不好看,而是三个问题:这套系统解决了什么实际问题?前后端技术选型为什么站得住脚?核心难点在哪里、你怎么实现的?这篇文章就围绕这三个问题展开,从角色权限、宠物领养状态机、后端事务控制、前端权限路由,一路讲到答辩现场的数据准备和追问应答。适合正在做毕设需要答辩的同学,也适合要对同类前后端项目做快速评审的从业者。
先说明我的立场:这套系统能不能拿到好成绩,七成在系统设计是否闭环,三成在答辩表达是否准确。所谓闭环,就是「送养人发布宠物 → 用户提交领养申请 → 管理员审核 → 宠物状态流转 → 领养完成」这条主线,中间任何一步断了,评委都会立刻觉察。下面按这个顺序拆。
2. 先让业务闭环站住:领养系统的角色、状态机与7张核心表
2.1 三种角色和一条主线:为什么把「申请中」单独做成一个状态
宠物领养和电商交易最大的区别在于:它不是下单即成交,中间多了一道人工审核。这个区别直接决定了你系统的数据模型长什么样。很多同学的第一个版本把宠物表设计成只有「待领养/已领养」两个状态,结果用户提交申请后宠物状态没变化,管理员审核通过后宠物才变成已领养,这套流程在逻辑上就缺了一环——申请提交后到审核通过前,宠物到底处于什么状态?别人还能不能申请?
我在做同类项目时一般会设计三个角色:普通用户、送养人、管理员。送养人和普通用户在实际操作中往往是同一张用户表通过角色字段区分,但业务上要分开理解。普通用户负责浏览宠物、提交申请、收藏;送养人负责发布宠物、上传图片、下架宠物;管理员负责审核领养申请、管理公告、查看操作日志。答辩PPT里画一张三角形角色图,说清这三者的权限边界,比堆十个页面截图有用得多。
核心的状态机设计如下:
| 对象 | 状态值 | 含义 | 触发动作 |
|---|---|---|---|
| 宠物 pet.status | 0 | 待领养 | 送养人发布/下架恢复 |
| 宠物 pet.status | 1 | 申请中 | 用户提交领养申请 |
| 宠物 pet.status | 2 | 已领养 | 管理员审核通过 |
| 申请 adopt.status | 0 | 待审核 | 用户提交申请 |
| 申请 adopt.status | 1 | 审核通过 | 管理员点击通过 |
| 申请 adopt.status | 2 | 审核驳回 | 管理员点击驳回 |
关键是「申请中」这个状态不能省。它承担着两个职责:一是给管理员一个明确的待办信号,二是从数据层面拦截重复申请。如果两个人同时申请同一只宠物,第二个人的请求到达后端时发现宠物status已经是1,就直接拒绝。这个设计在答辩现场几乎是必问点,你要能说出来「状态机是防并发领养的第一道闸门」。
2.2 七张核心表的字段设计:头一次建表就避开返工
数据表的设计很大程度上决定代码要返工多少次。我见过最典型的翻车案例是:所有宠物图片都塞在pet表的一个img字段里,用逗号分隔,结果做图片列表时前端解析麻烦,删除单张图片更是噩梦。正确的做法是把图片拆成独立表。下面这七张表是我做宠物领养系统时常用的核心结构。
-- 用户表 CREATE TABLE `user` ( `id` bigint PRIMARY KEY AUTO_INCREMENT, `username` varchar(32) NOT NULL UNIQUE, `password` varchar(128) NOT NULL, `nickname` varchar(32) DEFAULT NULL, `phone` varchar(20) DEFAULT NULL, `role` tinyint NOT NULL DEFAULT 0 COMMENT '0-普通用户 1-送养人 2-管理员', `avatar` varchar(255) DEFAULT NULL, `deleted` tinyint NOT NULL DEFAULT 0, `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP );-- 宠物表 CREATE TABLE `pet` ( `id` bigint PRIMARY KEY AUTO_INCREMENT, `name` varchar(64) NOT NULL, `type` tinyint NOT NULL COMMENT '0-猫 1-狗', `breed` varchar(64) DEFAULT NULL COMMENT '品种', `age` varchar(16) DEFAULT NULL COMMENT '年龄段,如 1岁以内', `gender` tinyint DEFAULT NULL COMMENT '0-公 1-母', `vaccinated` tinyint DEFAULT 0 COMMENT '0-未接种 1-已接种', `neutered` tinyint DEFAULT 0 COMMENT '0-未绝育 1-已绝育', `description` text COMMENT '领养要求与宠物描述', `status` tinyint NOT NULL DEFAULT 0 COMMENT '0-待领养 1-申请中 2-已领养', `owner_id` bigint NOT NULL COMMENT '送养人用户ID', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP );剩余五张表分别是:pet_image宠物多图表、adoption_application领养申请表、favorite收藏表、notice公告表、operation_log操作日志表。其中adoption_application的核心字段包括pet_id、user_id、reason申请理由、address常驻地址、experience养宠经验、status审核状态、reviewed_by审核人ID、reviewed_at审核时间。operation_log建议记录操作人、操作类型、目标对象、操作时间和IP,后面用AOP统一写入,答辩时可以作为加分项。
字段设计上有两个容易被问倒的细节。第一个是逻辑删除:用户注销账号后,他发布的宠物和申请记录不应该物理消失,否则管理员的历史审核记录就对不上号了,所以每张业务表都加deleted字段。第二个是索引:pet表的status和type是查询高频条件,建议建联合索引;adoption_application表的pet_id和user_id分别建普通索引,否则申请量上来后按宠物查申请记录会慢。
2.3 答辩讲数据设计的话术:从ER图讲到主外键与索引
答辩PPT里数据设计这部分,不要贴建表SQL,评委没时间逐行看。正确做法是放一张ER图,图里标清楚用户到宠物的一对多、宠物到申请记录的一对多、用户到申请记录的一对多这三条核心关系,然后用一分钟讲明白三个点。
第一个点是:为什么用户和送养人不拆成两张表?因为绝大多数用户既可能发布宠物也可能申请领养别人发布的宠物,拆成两张表反而让角色转换变得复杂,用一个role字段区分更符合实际场景。第二个点是:为什么申请记录和宠物状态分两张表维护?因为申请记录是流水数据,要保留审核历史和驳回原因,而宠物状态是实时数据,两者生命周期不同,混在一张表里要么丢历史要么查状态慢。第三个点是:设计时为什么所有表都用自增主键?因为这套系统的写入并发量不高,自增主键索引写入性能好,实现也简单,没必要引入雪花算法增加答辩复杂度。
数据设计讲到这里,PPT就可以翻页了。评委如果追问「为什么不做成三张表」「索引怎么设计的」,上面的内容足够应付。接下来要进入评委更感兴趣的部分——后端核心接口到底怎么写。
3. SpringBoot后端两个核心接口:领养申请与审核的事务写法
3.1 创建领养申请:校验、防重复与宠物状态抢占
后端接口里最容易被评委深挖的就是「领养申请」这个动作。它涉及三件事:校验申请条件、防止重复提交、把宠物状态从未申请改为申请中。如果直接写一个简单的insert,那必然会在并发场景下出问题。我一般会这样做。
@PostMapping("/api/adoptions") public Result<Long> apply(@RequestBody AdoptionApplyDTO dto, @RequestAttribute("userId") Long userId) { // 1. 防重复:同一用户对同一宠物只能存在一条待审核申请 Long exists = adoptionMapper.selectCount( new LambdaQueryWrapper<AdoptionApplication>() .eq(AdoptionApplication::getPetId, dto.getPetId()) .eq(AdoptionApplication::getUserId, userId) .eq(AdoptionApplication::getStatus, 0)); if (exists != null && exists > 0) { return Result.fail("你已经申请过这只宠物,请等待审核结果"); } // 2. 抢占宠物状态:UPDATE 条件更新,影响行数为0说明已被别人申请 int updated = petMapper.casUpdateStatus(dto.getPetId(), 0, 1); if (updated == 0) { return Result.fail("手慢了,这只宠物刚被别人申请了"); } // 3. 插入申请记录 AdoptionApplication app = new AdoptionApplication(); app.setPetId(dto.getPetId()); app.setUserId(userId); app.setReason(dto.getReason()); app.setAddress(dto.getAddress()); app.setExperience(dto.getExperience()); app.setStatus(0); adoptionMapper.insert(app); return Result.success(app.getId()); }重点在于casUpdateStatus这个方法。它的SQL大致是UPDATE pet SET status = 1 WHERE id = #{petId} AND status = 0,数据库层面保证只有状态为0的宠物能被置为申请中,影响行数为0就说明有人抢在前面提交了。这比先用select查询再判断要可靠得多,因为select和update之间存在时间窗口,两个人可能同时通过查询校验。我在这类系统中一贯要求:状态变更一律用条件更新,禁止先查再改。
@RequestAttribute("userId")的值来自登录拦截器解析JWT后写入的请求属性,这样业务代码不需要关注当前用户是谁,属于比较干净的写法。这里还需要注意事务边界:状态抢占和申请记录插入应该放在同一个事务方法里。如果宠物状态更新成功但申请记录插入失败,事务回滚,宠物状态恢复为0,不会出现「宠物变申请中但查不到申请记录」的脏数据。用@Transactional标注这个service方法即可。
3.2 审核通过:一个事务里改两张表,但要用条件更新兜底
审核接口是第二个必问点。一个典型的设计失误是把审核逻辑写成:查询申请记录 → 判断状态 → 更新申请状态 → 更新宠物状态。四个步骤中间任何一步异常,都会造成数据不一致。而且先查再改在并发场景下存在隐患——两个管理员同时打开一条待审核申请,都通过了校验,结果申请被处理两次。正确的写法是先条件更新申请状态,影响行数为1才继续更新宠物。
@PostMapping("/api/adoptions/{id}/approve") public Result<Void> approve(@PathVariable Long id, @RequestAttribute("userId") Long adminId) { // 1. 条件更新:只能审核 status=0 的申请 int updated = adoptionMapper.casApprove(id, adminId); if (updated == 0) { return Result.fail("该申请已被处理,请勿重复操作"); } // 2. 更新宠物状态为已领养 AdoptionApplication app = adoptionMapper.selectById(id); petMapper.updateStatusDirect(app.getPetId(), 2); return Result.success(); }对应的Mapper方法用@Update注解直接写条件SQL,例如:
@Update("UPDATE adoption_application SET status = 1, reviewed_by = #{adminId}, " + "reviewed_at = NOW() WHERE id = #{id} AND status = 0") int casApprove(@Param("id") Long id, @Param("adminId") Long adminId);驳回接口的写法完全一样,只是把status改为2,同时把驳回原因写入申请表的reject_reason字段,并释放宠物状态,让宠物回到0待领养状态。这里要特别注意:审核通过后宠物状态是2已领养,审核驳回后宠物状态应该恢复成0,两个动作的宠物状态更新方向是完全相反的,写错一个就出现「宠物被驳回了却不能再被申请」的尴尬局面。
事务注解加在整个审核方法上,方法内第一步条件更新失败就抛异常触发回滚,不会有部分成功的情况。这是MySQL默认的REPEATABLE READ隔离级别下也能安全运行的写法,因为条件更新本身是原子的。
3.3 图片上传与访问映射:后端最容易忽略的配置
宠物领养系统的图片上传看起来简单,实际上特别容易在答辩演示时翻车。典型现象是开发环境图片正常显示,部署到演示机或者换个端口启动后,所有图片全部404。原因通常是上传目录配置成了项目相对路径,而资源映射没做。我一般会用一个绝对路径存放上传文件,然后通过配置类映射访问URL。
file: upload-path: /data/pet-adoption/upload/@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Value("${file.upload-path}") private String uploadPath; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + uploadPath); } }上传接口接收MultipartFile,保存时按日期分目录存储,文件名用UUID重命名,避免中文名乱码和重名覆盖。代码大致是:
String dir = uploadPath + LocalDate.now().toString(); Files.createDirectories(Paths.get(dir)); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); String filename = UUID.randomUUID().toString().replace("-", "") + ext; file.transferTo(Paths.get(dir, filename).toFile());图片访问路径存到pet_image表后,前端拿到的就是/upload/2025-06-01/xxx.jpg这样的相对路径。答辩PPT里如果讲到文件上传,建议把「路径不在项目内部、通过资源映射对外提供访问」这个设计作为一个小亮点带过,评委通常会觉得你考虑过部署问题。
3.4 这些接口在答辩时怎么讲
后端代码展示环节,不要贴所有Controller,选两段就够了:一段是领养申请接口,另一段是审核接口。讲的时候按「问题 → 方案 → 代码验证」三段式。问题就讲并发重复申请,方案就讲状态机加条件更新,代码就贴casUpdateStatus那三行核心SQL。评委会觉得你有真实防御意识,而不是只会写CRUD。
4. Vue前端两个关键交互:权限路由与领养申请表单
4.1 路由守卫与菜单权限:用户和管理员各看各的
前端部分最容易在答辩时被问到的就是权限控制。很多同学的实现是后端返回菜单列表,前端用v-if判断角色显隐。这种方式有个明显弱点:路由仍然在前端注册表里,懂一点前端的人可以直接访问管理页面的路由地址绕过菜单限制。正确做法是路由级拦截,在router.beforeEach里根据登录状态和角色判断是否放行。
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') const role = localStorage.getItem('role') if (to.meta.requiresAuth && !token) { next({ path: '/login', query: { redirect: to.fullPath } }) return } if (to.meta.requiresAdmin && role !== 'ADMIN') { next('/') return } next() })对应的路由定义里,在需要管理员权限的页面加上meta: { requiresAuth: true, requiresAdmin: true },用户首页只需要requiresAuth: true,登录页和宠物列表页则不需要。这套方案在答辩时讲起来很清晰:前端的每一个受保护页面都显式声明需要的权限,路由守卫统一拦截,而不是分散在各个组件里判断。菜单显隐只是视觉上的,真正卡住权限的是路由守卫。
实际开发中我一般会把role字段也用常量管理,比如const ROLE_ADMIN = 'ADMIN',避免魔法字符串散落在代码各处。如果后端返回的是数字角色,前端做好映射再存,这样判断时不容易出错。
4.2 领养申请表单:二次确认与状态拦截
领养申请表单本身不复杂,复杂的是它的前置状态判断和后置交互反馈。用户从宠物详情页点「申请领养」时,前端需要先确认宠物当前状态还是待领养,如果已经变成申请中或已领养,按钮要置灰或点击后给出明确提示。不然用户提交后得到一个后端错误,体验很差。
const submitApply = async () => { if (petDetail.value.status !== 0) { ElMessage.warning('这只宠物已经被申请或领养了') return } await ElMessageBox.confirm( '请确认你已认真阅读该宠物的领养要求,提交后需等待管理员审核。', '确认提交申请' ) await applyApi.create({ petId: petDetail.value.id, reason: form.value.reason, address: form.value.address, experience: form.value.experience }) ElMessage.success('申请已提交,请耐心等待审核') fetchPetDetail() }二次确认这里不是走过场,它能挡住两类问题:一是用户误触导致重复提交,二是用户没看清领养要求就提交,审核时被驳回又产生客服沟通成本。fetchPetDetail()在提交成功后重新拉取宠物详情,是为了让页面上的状态值立刻从0变成1,按钮同步变成「已申请,等待审核」的禁用态。
表单校验推荐放在提交前做一次完整校验,而不是让用户逐个字段失去焦点时才提示。我用el-form的rules校验require字段:申请理由必填且不少于10个字,联系地址必填,养宠经验选填但建议填写。答辩时如果有人问「为什么前端也要校验」,标准回答是:前端校验改善交互效率、降低无效请求,后端校验保证数据安全和业务正确,两层各司其职。
4.3 管理端审核列表:分页、状态标签与操作按钮
管理端审核页是评委大概率会点开看的页面。它的核心交互是三个:分页加载、状态标签渲染、审核操作按钮按状态显隐。很多同学会在这里写一堆v-if判断,代码又长又容易出错。更简洁的做法是定义一个状态映射对象,模板里直接查表。
const statusMeta = { 0: { label: '待审核', type: 'warning' }, 1: { label: '已通过', type: 'success' }, 2: { label: '已驳回', type: 'danger' } }<el-table :data="page.records"> <el-table-column prop="petName" label="宠物" width="140" /> <el-table-column prop="nickname" label="申请人" width="120" /> <el-table-column prop="reason" label="申请理由" show-overflow-tooltip /> <el-table-column label="状态" width="100"> <template #default="{ row }"> <el-tag :type="statusMeta[row.status]?.type || 'info'"> {{ statusMeta[row.status]?.label || '未知' }} </el-tag> </template> </el-table-column> <el-table-column label="操作" width="180"> <template #default="{ row }"> <template v-if="row.status === 0"> <el-button type="success" size="small" @click="handleApprove(row)">通过</el-button> <el-button type="danger" size="small" @click="handleReject(row)">驳回</el-button> </template> <span v-else class="text-muted">已处理</span> </template> </el-table-column> </el-table>操作列用v-if="row.status === 0"控制,一旦审核过,按钮消失,从交互层面防止重复操作。分页参数是pageNum和pageSize,后端返回total、records这样的结构。这里有一个前端常见的坑:Element Plus 的分页组件页码从1开始,而后台接口如果习惯从0开始,初始化时要把页码减一或统一约定从1开始,否则第一页数据正常、第二页数据会错位。我统一约定接口pageNum从1开始,省去一堆转换逻辑。
管理端页面答辩演示时还要注意一个细节:先展示一条待审核数据的完整审核流程,然后马上切到宠物列表页刷新,让评委看到宠物状态同步变成了已领养。这个联动效果是后端事务正确性的直观证明,比嘴上说八百遍「我用事务保证了数据一致」更有说服力。
5. 答辩现场最容易翻车的5个技术坑:现象、原因与抢救方案
5.1 图片全部404?静态资源映射没配全
现象:本地开发时图片显示正常,打包部署到演示环境后,页面上的宠物图片全部裂开,控制台报404。
原因:开发环境启动时,项目工作目录内有upload文件夹,IDE会当成静态资源处理;部署环境使用java -jar启动后,上传目录在项目jar包之外,Spring Boot 默认不映射外部目录到URL路径。
解决:在配置类中给/upload/**增加资源处理器,指向实际的上传绝对路径。答辩前一天用「打包后的jar包启动」完整测一遍图片上传与回显流程,不要只在IDE里验证。另外检查数据库里存的图片路径是否带域名前缀,如果存的是http://localhost:8080/upload/xxx.jpg,部署到服务器后端口一变就彻底无法访问,存相对路径并在前端拼接口地址是更稳妥的做法。
5.2 宠物被两个人同时申请成功?状态抢占没做
现象:开两个浏览器窗口,用两个账号同时点「申请领养」同一只宠物,两个请求都提示成功,宠物状态变成申请中,但申请记录有两条。
原因:后端先select查询有没申请,再insert申请记录、update宠物状态。两个请求同时通过select校验,自然都执行成功。这是典型的先查后改并发问题。
解决:用条件更新抢占,UPDATE pet SET status = 1 WHERE id = ? AND status = 0,影响行数为0就拒绝第二个请求。补救方法是给宠物表加一个唯一索引约束,比如(pet_id, user_id),但这只能挡住同一用户重复申请,挡不住两个不同用户同时申请。核心还是状态抢占,这个代码逻辑必须在答辩前测试过。
5.3 接口返回401,前端页面毫无反应?Token过期没统一处理
现象:演示过程中,用户在页面停留了较长时间后点「提交申请」,页面一直转圈,控制台显示接口返回401,但界面没有任何提示。
原因:JWT过期后请求被拦截器拦截返回401,前端axios响应拦截器没有针对401做统一跳转,导致用户以为系统卡死。
解决:在axios响应拦截器里额外处理401状态,清除本地登录态并跳转登录页,带上当前路由作为登录后的回跳地址。答辩现场的即时抢救方法:先打开浏览器开发者工具的Network面板,截图给评委看401响应,然后解释「这是Token过期保护,重新登录即可恢复」,同时演示一遍重新登录流程。提前把这个场景想好,不但不翻车,还能变成安全机制演示。
5.4 事务没回滚?自调用导致代理失效
现象:审核通过的接口在「更新申请状态成功、更新宠物状态失败」时没有回滚,数据出现不一致。本地联调时报错但事务没生效。
原因:@Transactional在Spring中是AOP代理实现的,只有通过代理对象调用方法时事务才生效。如果在同一个类里this.approve()这样调用,绕过代理,事务注解直接失效。我见过的情况是有人在Controller里写业务逻辑,service方法内部互相调用,结果事务形同虚设。
解决:事务方法必须通过注入的Service Bean调用,不要同类内部自调用。确实需要自调用时,可以注入AopContext.currentProxy()获取代理对象,或者拆出独立的事务方法。答辩现场如果评委问「你这个事务真的生效吗」,快速回答重点:事务加在service层应用入口方法上,外层调用方是Controller注入的代理Bean,不是this自调用。
5.5 演示时列表空白?初始化数据与分页参数不匹配
现象:现场演示时宠物列表只有第一页有数据,点第二页就空白,或者列表底部显示总共只有5条数据、却分成了2页。
原因:后端分页接口返回了total和records,但前端分页组件的页码和页大小参数名没对齐,比如后端约定pageNum前端传了pageNo,导致后端拿不到分页参数直接返回全量数据或空数据。
解决:前后端分页参数名统一用pageNum/pageSize,且在application.yml里配置MyBatis-Plus分页插件,写一个分页配置类注入拦截器。答辩演示前一定要准备超过一页的预置数据,比如10条宠物记录配合每页5条的展示,现场翻页给评委看,比嘴上说「支持分页」有说服力得多。
6. 答辩PPT与技术演示:十分钟讲清系统、避开三类追问
6.1 演示顺序与PPT结构:先用户后管理、先结果后代码
答辩现场的PPT页数控制在12页左右最合适。结构按「背景痛点 → ER图 → 系统架构图 → 角色权限 → 核心流程 → 核心代码 → 演示截图 → 总结展望」推进。第一页讲项目背景时,一句话点出「线下领养信息不透明、流程不可追踪」即可,不要写长篇大论。
演示环节建议严格按这个顺序走:先演示注册登录,用普通用户账号进入首页;接着浏览宠物列表,点开一个细节最丰富的宠物详情;然后提交领养申请,切管理员账号进入审核列表,通过申请;最后切回用户视角刷新列表,宠物状态已经变为已领养。整个流程控制在五分钟以内,节奏是「展示结果、再解释设计」。技术亮点放在系统演示之后讲,重点讲三件事:状态机设计、条件更新防并发、JWT无状态认证。
有个习惯我保持了很多年:答辩前一天把所有演示数据重新导一次,确保账号密码、宠物状态、申请记录全都符合演示脚本预期。现场演示最怕的不是代码bug,而是预置数据在多次测试后被改得面目全非,到了答辩时找不到一条「待审核」的申请记录。
6.2 评委必问的三个问题与应答思路
第一个高频问题是「为什么选SpringBoot+Vue这个组合」。不要只说「因为大家都在用」。合理应答是:SpringBoot的起步依赖和自动配置让后端开发聚焦业务逻辑,内置Tomcat让部署只需要一个jar包;Vue的组件化和响应式数据绑定让前后端可以并行开发,双方通过Restful接口和JSON交换数据。如果评委追问为什么不选Next.js或者SSR方案,可以承认「这套系统对SEO没有要求,管理后台和用户端都是登录后使用,CSR完全够用」。
第二个高频问题是「JWT和传统Session有什么区别」。应答要点是:Session存储在服务器内存中,分布式部署时要引入共享存储;JWT把用户信息加密后放在客户端,服务端无状态、天然支持水平扩展。同时承认JWT的缺点:无法在服务端主动失效,所以退出登录要靠前端清除本地Token,这也是为什么本项目把登录拦截逻辑放在网关层和路由守卫两层。
第三个高频问题是「如何保证领养数据的准确性」。这里把状态机设计、条件更新、事务回滚三件事串起来回答:宠物状态只有通过条件更新才能流转,申请记录插入和状态更新在一个事务中,审核操作通过条件更新防止重复处理。回答时拿申请接口的casUpdateStatus代码做佐证,评委能看到你确实想清楚了并发场景。
最后一件事:准备一个「如果接口全挂了」的兜底方案。比如演示数据库连接断了,可以先口述系统设计,再打开开发者工具展示接口文档。答辩不是为了证明代码毫无缺陷,而是证明你对系统的每一个模块都有清晰的判断。把上面这些难点逐个演练一遍,你会发现自己能坦然应对大半问题。希望帮到你。
本文还有配套的精品资源,点击获取