一个能跑起来的毕设,比花里胡哨的架构重要得多。
SpringBoot + Vue 的宠物领养系统,算是我见过的高校毕设/课设里最稳妥的选择之一。技术栈主流、业务逻辑清晰、演示效果直观,最关键的是——它不会在答辩时让你陷入“为什么这里要这么复杂”的尴尬。市面上流传的版本很多,但大部分要么代码老旧(还在用 JSP),要么前后端不分家(Vue 和 Java 代码揉在一起),要么数据库设计一塌糊涂。所以我决定基于实际开发经验,把这类项目的完整构建思路、核心代码逻辑、踩过的坑,一次性说清楚。
1. 项目整体设计与技术选型思路
1.1 为什么是 SpringBoot + Vue 这对组合
先聊选型。宠物领养系统这种业务,本质上是典型的 CRUD 加一点业务状态流转,但它又比纯后台管理系统多了一层“信息公开”和“用户交互”的诉求。用 SpringBoot 做后端,看中的是它对 Spring 生态的整合能力——不用像 SSM 时代那样配一堆 XML,注解驱动,内嵌 Tomcat,打成 jar 包直接跑。这对学生来说太友好了,本地开发调试的复杂度直线下降。前端选 Vue,道理也简单:组件化开发让页面拆分成领养列表、宠物卡片、申请表单这种独立模块,代码组织清晰,而且 Vue 的生态足够成熟,Element UI 一挂,后台管理界面的颜值立刻就有了保障。
这里有一个很实际的建议:如果你的毕设题目是“基于XX的宠物领养平台”,直接用这个组合准没错。它不是最优解,但它是最不容易出错的解。
1.2 系统角色划分与业务流程
任何系统先定角色,再定流程。宠物领养系统里,最常见的角色划分是三种:普通用户(访客/领养人)、管理员、以及可能存在的收容机构/送养人。从毕设答辩的角度看,三角色模型最完整,也能把“权限管理”这块的分数拿到手。但如果你时间不够,砍掉送养人角色,只保留“用户”和“管理员”也是完全可以的,业务闭环依然成立。
核心业务流就一条主线:宠物信息发布 -> 用户浏览/搜索 -> 提交领养申请 -> 管理员/送养人审核 -> 线下交接确认 -> 领养状态完结。
另一个隐藏支线是“寻主/寻宠”功能,不过我建议量力而行。基础版做好领养主线加评论互动就够了,支线做不好反而显得功能臃肿。
1.3 为什么这个项目适合毕设/课设学习
说实话,宠物领养系统的代码量适中,但涉及的知识点非常“全”。
- 后端覆盖了 SpringBoot 的自动配置、MyBatis Plus 的 CRUD 封装、拦截器做登录校验、定时任务(比如自动取消超时未处理的申请)等。
- 前端覆盖了 Vue Router 路由守卫、Axios 拦截器、Element UI 表单校验、跨组件通信(Vuex 或 Pinia)。
- 数据库层面有较多的表关联查询,比如宠物表和领养申请表、用户表之间的多表联合查询。
- 项目天然适合“讲故事”:可以从“流浪动物问题”切入,引入“互联网+公益”的概念,这在答辩时的选题背景介绍环节非常加分。
我见过很多同学的毕设用了电商系统、宿舍管理系统,同质化严重。宠物领养系统虽然不算冷门,但它自带“情感关怀”属性,在面向非技术评委(比如经管类老师)演示时,接受度明显更高。
2. 数据库设计与后端核心实现拆解
2.1 数据库表结构设计(不走弯路版)
数据库是这类项目的命根子。表设计不好,后期写 SQL 会被各种 join 折磨。我建议至少建这几张表。
第一张是user表,字段包括用户 ID、用户名、密码(BCrypt 加密后存储)、手机号、邮箱、头像 URL、角色标识(0 普通用户 / 1 管理员 / 2 送养人)、注册时间。注意用户名一定要加唯一索引,这是登录系统的底线。
第二张是pet表,承载核心业务。字段必须有宠物 ID、宠物名称、种类(猫/狗/其他)、品种、年龄(月份)、性别、绝育状态、疫苗状态、性格描述、健康状态、图片 URL(建议存多个,用逗号分隔或单独建图库表)、所在城市、发布人 ID(关联 user 表)、领养状态(0 待领养 / 1 申请中 / 2 已领养)、浏览量、创建时间。这里面最重要的字段是“领养状态”,它是整个业务流转的开关。
第三张是adoption_apply领养申请表,记录用户提交的领养意向。字段为申请 ID、宠物 ID、申请人 ID、申请理由(文本框,这个是审核依据)、个人情况说明(住房/工作等)、申请状态(0 待审核 / 1 通过 / 2 拒绝)、创建时间、审核回复。有些同学会忽略说明字段,这是不对的——审核逻辑全靠它支撑,否则管理员凭什么通过或拒绝?
第四张是favorite收藏表,做用户互动用的。内容简单:ID、用户 ID、宠物 ID、收藏时间。联合唯一索引(用户ID + 宠物ID)必须加,防止重复收藏。
第五张可选的comment评论表,关联宠物 ID。如果时间紧张,这个表可以先砍掉,用申请理由替代互动功能。
2.2 SpringBoot 后端分层与核心注解
后端的包结构建议这样分:controller、service(接口+实现)、mapper、entity、config、common(统一返回结果封装)、exception(全局异常处理)。
技术选型上,MyBatis Plus 比 MyBatis 更合适。为什么?因为 MyBatis Plus 的BaseMapper直接提供了selectPage、selectById、updateById这些通用方法,对于宠物列表的分页查询、用户信息的更新这类高频操作,你根本不需要写 XML。省下来的时间用来打磨复杂的领养审核逻辑,回报率高得多。
举一个核心代码示例,宠物分页查询接口:
@GetMapping("/list") public Result<IPage<PetVO>> petList(@RequestParam(defaultValue = "1") Integer current, @RequestParam(defaultValue = "10") Integer size, String keyword, String type) { Page<Pet> page = new Page<>(current, size); LambdaQueryWrapper<Pet> wrapper = new LambdaQueryWrapper<>(); // 关键字搜索名称或描述 if (StringUtils.hasText(keyword)) { wrapper.and(w -> w.like(Pet::getName, keyword) .or().like(Pet::getDescription, keyword)); } // 按宠物种类筛选 if (StringUtils.hasText(type)) { wrapper.eq(Pet::getType, type); } // 只查询待领养状态的宠物 wrapper.eq(Pet::getStatus, 0); // 按发布时间倒序 wrapper.orderByDesc(Pet::getCreateTime); IPage<Pet> petPage = petMapper.selectPage(page, wrapper); // 这里需要做一次 VO 转换,把发布人的昵称、头像信息填充进去 return Result.success(convertToVO(petPage)); }这段代码的逻辑很直白:分页参数 + 关键字 + 类型筛选。注意一点:“只查询状态为 0(待领养)”这个条件是必须的,因为一旦宠物已经被领养,就不该再出现在公开列表里。很多初学者在这里漏掉状态过滤,导致已领养的宠物还在页面上挂着,相当尴尬。
2.3 领养审核的状态机设计
领养申请是整个系统的核心流转环节。我用一个状态机来管理,比粗暴的 if-else 强得多。
状态定义如下:申请提交(APPLYING = 0)、管理员通过(PASS = 1)、管理员拒绝(REJECT = 2)、用户取消申请(CANCEL = 3)、确认领养完成(DONE = 4)。
状态流转的允许路径是:
- APPLYING -> PASS(审核通过)
- APPLYING -> REJECT(审核拒绝)
- APPLYING -> CANCEL(用户主动撤销)
- PASS -> DONE(线下交接,确认领养完成)
这里最关键的防护在于“宠物状态”和“申请表状态”必须保持联动。当一个宠物被某条申请单 PASS 后,宠物本身的status要立刻从 0(待领养)改成 1(申请中)或直接改成 2(已领养)。否则会出现用户对同一宠物重复提交申请的情况,业务就乱套了。
给一个伪代码逻辑参考:
@Transactional public Result<String> reviewApply(Integer applyId, Integer passOrReject, String reply) { AdoptApply apply = applyMapper.selectById(applyId); // 校验状态,防止重复审核 if (apply.getStatus() != 0) { return Result.fail("该申请已被处理,请勿重复操作"); } // 更细粒度:这里还需要校验宠物当前状态,确保没有被其他人抢先领养 Pet pet = petMapper.selectById(apply.getPetId()); if (pet.getStatus() != 0) { return Result.fail("该宠物已被申请,请刷新列表"); } // 更新申请状态 apply.setStatus(passOrReject); apply.setReply(reply); applyMapper.updateById(apply); // 更新宠物状态:通过则置为已领养;拒绝则保持在待领养 pet.setStatus(passOrReject == 1 ? 2 : 0); petMapper.updateById(pet); return Result.success("操作成功"); }一定要加@Transactional注解,保证申请单状态和宠物状态要么同时成功,要么同时回滚。这个在答辩时是很好的拓展点:主动讲出“用事务保证数据一致性”,比评委问一句你答一句要主动得多。
2.4 登录认证与权限控制
JWT 是目前这类前后端分离项目的事实标准,没有之一。核心思路是:用户登录成功后,后端签发一个令牌,前端把它存到 localStorage 里,每次请求在请求头里带上Authorization: Bearer <token>,后端用拦截器校验并解析出用户身份。
具体配置分三步。第一步,写一个 JWT 工具类,包含生成令牌、解析令牌、校验过期时间三个静态方法。第二步,写一个拦截器,继承HandlerInterceptorAdapter或者实现HandlerInterceptor接口,在preHandle里做统一鉴权。对于白名单路径(比如/user/login、/pet/list、/pet/detail)直接放行;其他路径一旦检测不到有效令牌,返回 401 状态码。第三步,在WebMvcConfigurer里注册这个拦截器,并指定拦截路径addPathPatterns("/**")。
前端统一处理后端返回的 401,在 Axios 响应拦截器里判断 code 是否为 401,如果是就清除本地登录状态并跳转回登录页,实现还算顺滑的“会话失效”处理。这里有个小坑要注意:Vue 的登录页跳转用router.push,不要在拦截器里用window.location.href强行刷新,否则刷新后 Vue 状态丢失,来回跳转体验很割裂。
2.5 文件上传的本地存储方案
宠物图片是核心展示元素,毕设项目不推荐一开始就接云存储(OSS),那是给自己找麻烦。用本地存储足够:后端的配置文件中设置一个自定义上传路径,比如file.upload-path,通过MultipartFile接收文件,把图片存到指定目录。访问时通过虚拟路径映射到磁盘目录。
具体做法是,在WebMvcConfigurer里重写addResourceHandlers,把/images/**映射到本地磁盘路径。这样前端只需要/images/pet/20250612.jpg就能访问图片,不用走经过拦截器的接口,也不用担心权限问题。但有个细节必须注意:这个/images/**路径必须加入拦截器的白名单。我自己最开始做的时候漏了这一步,结果前端图片加载不出来,控制台报 401,排查了半天才发现是拦截器把图片请求拦住了。
如果你想把项目做得更精致一点,可以顺手加一个简单的图像压缩逻辑,用 Java 自带的ImageIO把上传的大图压缩到宽度不超过 800 像素再落盘,这样列表页加载会明显变快,算是个小而美的加分项。
3. Vue 前端搭建与核心交互实现
3.1 项目初始化与目录结构规划
前端用 Vue CLI 或 Vite 初始化项目骨架都可以。我个人倾向 Vite,启动速度快很多,而且配置比 Webpack 直观。目录结构上按模块分包是最合理的,比如:
src/api—— 按业务模块拆分的请求函数(pet.js、user.js、apply.js),推荐的写法是每个文件对应一个后端 Controller。src/router—— 路由配置,这里要写明每个路由的元信息,哪些需要登录、哪些是管理员专属。src/store—— Pinia 或 Vuex 的状态管理,主要存用户信息和登录状态。src/views—— 页面级组件,比如首页、宠物详情、领养申请表单、后台管理、用户中心。src/components—— 可复用的UI组件,比如宠物卡片、分页组件、图片轮播。
3.2 宠物列表页的渲染与搜索
列表页的视觉效果决定了评委的第一印象。卡片式布局是这类网站的主流。每个卡片显示宠物图片、名字、年龄、疫苗状态,鼠标悬浮显示字数限制的简介。不要在这个页面上展示太多字段,那是详情页的事。
搜索和筛选区,我用 Element UI 的el-form配合el-select和el-input实现。宠物种类下拉框选项写死(猫/狗/其他),无需走后端接口。点击查询按钮后,把表单数据作为参数调用api.pet.getList(params),拿到结果重新渲染卡片。
这里有一个开头说过的易错点:筛选条件提交给后端时,请不要直接用 undefined 或空字符串。Axios 的params序列化如果带了空参数,后端会拿空字符串去匹配数据库,结果就是原本应该查全部,结果什么都查不到。建议做一层参数清洗:
function cleanParams(params) { const result = {}; Object.keys(params).forEach(key => { if (params[key] !== '' && params[key] !== null && params[key] !== undefined) { result[key] = params[key]; } }); return result; }3.3 领养申请表单的交互与校验
用户点击“申请领养”按钮时需要先判断登录状态。前端可以在按钮点击处理函数中用store.state.userInfo判断,未登录则弹出确认框提示去登录;已登录则打开一个对话框,内嵌领养申请表。
申请表单至少要有两个字段:申请理由(textarea)、个人情况说明(input)。不建议再加什么身份证号、月收入之类的敏感字段,一是用户反感,二是有合规风险,毕设展示没必要趟这种浑水。
Element UI 的表单校验规则要加好。理由字段设为必填,最少 10 个字,校验器如下:
rules: { reason: [ { required: true, message: '请填写领养理由', trigger: 'blur' }, { min: 10, message: '领养理由至少10个字', trigger: 'blur' } ] }提交成功后关闭对话框,同时前端要把当前宠物在列表中的状态置为“申请中”。这个细节很重要,体现的是“逻辑一致”的理念:申请一旦提交,这个宠物就不该再被其他人重复申请了,哪怕只是前端状态位,也要跟着变。我见过不少项目提交后前端一点反馈都没有,用户以为没成功,又点了几次,后端由于缺少幂等性控制,生成了好几条重复申请,烦人得很。
3.4 后台管理界面要点
后台管理采用独立的 Layout,左侧菜单放置审核管理、宠物管理、用户管理等模块。宠物管理表格中,管理员可以对宠物信息进行编辑或下架(把 status 改成下架状态,而不是删除记录)。审核管理页面是工作台风格的待审核列表,每行有“通过”或“拒绝”按钮,点击后弹出确认框,如果是拒绝,会弹出一个输入框让管理员填写拒绝理由。这些交互都用 Element UI 的el-dialog和el-message-box完成,代码不复杂,但要跑通一遍,体验要达到“点击-确认-列表刷新-状态更新”的全链路闭环。
3.5 路由守卫
管理员的权限校验,不能只在菜单显示上做文章,必须在路由层面拦住。
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (to.meta.requiresAuth) { if (!token) { next('/login'); return; } if (to.meta.requiresAdmin) { const role = store.state.userInfo?.role; if (role !== 1) { next('/403'); return; } } } next(); });这个守卫写得逻辑简单,对接后端拦截器是双保险。前端守卫是拦截页面跳转,后端拦截器是拦截接口请求,两个必须同时存在,缺一个都不安全。而且,后端接口必须做角色校验,不能只靠前端隐藏页面来保证安全,这算是安全管理方面的底线思维。
4. 项目部署、演示与常见问题排查
4.1 本地快速跑起来的步骤
环境要求:JDK 8+(建议 8 或 11,稳妥)、Maven 3.6+、MySQL 5.7+(或 8.0)、Node.js 14+。
第一步,把源码导入 IDE。后端用 IDEA,设置一下 JDK 版本,Maven 仓库加载依赖。这一步如果网速慢,可以考虑用阿里云的 Maven 镜像,否则下载依赖可能耗时很久。
第二步,准备数据库。新建名为pet_adoption的数据库,执行项目sql目录下的初始化脚本,里面会有建表语句和初始数据(包括一个测试用的管理员账号:admin / admin123)。
第三步,修改后端配置文件application.yml,核心是数据库连接串:
spring: datasource: url: jdbc:mysql://localhost:3306/pet_adoption?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的密码第四步,启动后端。正常运行后,控制台会输出端口号,默认 8080。
第五步,前端启动。命令行进入前端项目目录,依次执行npm install(如果你用 yarn 就是yarn),然后npm run serve。默认端口是 8081 或 5173,浏览器访问即可。
4.2 前端联调时的跨域问题怎么解决
前后端分离必踩的坑:跨域。开发环境下,前端访问http://localhost:8081,后端是http://localhost:8080,浏览器默认拦截跨域请求。解决方法有两种。
第一种,后端加全局跨域配置:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }第二种,前端配置代理。Vite 的配置在vite.config.js中:
server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } } }这两种方式效果一样。我一般建议都在开发环境里用代理方式解决,因为部署到服务器之后,通常是 Nginx 反向代理负责这一层对接,开发环境和生产环境的访问路径保持一致,前端代码不需要改任何一行,省心。
4.3 常见逻辑报错与解决速查表
分享几个我调试时排查过的经典问题,基本都是做这类项目绕不开的点。
MySQL 时区报错,启动后端就报异常,提示The server time zone value 'OS timezone'。原因是连接串里serverTimezone没配。按上面配置里加&serverTimezone=Asia/Shanghai即可。用的是旧版本驱动的话写成serverTimezone=GMT%2B8也是等效的。
前端图片加载不出来,F12 看到 401。这个上文提过,就是拦截器把静态图片路径拦截了。解决方法是把/images/**加入白名单,或者把HandlerInterceptor的拦截路径精确到接口路径。
前端请求成功但数据不渲染。排查思路:先看网络面板返回的 JSON 结构,再检查后端返回的Result类结构是否和前端 Axios 拦截器里解析结构一致。常见坑:后端返回{ code: 0, data: [...] },前端拦截器判断res.data.code === 200,恒为false,导致Promise.reject被抛出。这类错误多发生在拷贝的代码里,前后端状态码约定不一致。统一约定为:前端判断后端 code 是否为 200,是则取res.data.data,否则提示错误信息。
删除宠物失败,外键约束报错。原因一般是有子表关联,比如领养申请表引用了宠物ID。正确的做法是逻辑删除(把宠物 status 改成 2),而不是物理删除记录,从功能角度和数据库安全性角度都更合理。
JWT 过期后没有任何提示,用户以为自己还处于登录态。前端 Axios 响应拦截器里必须加上对 401 状态的统一处理。这段代码虽然短,但价值很高,因为评委试用系统时如果点着点着发现“页面看起来还能访问,但接口全报错”,印象分会掉一大截。
// axios 响应拦截器 service.interceptors.response.use( (response) => { const res = response.data; if (res.code !== 200) { if (res.code === 401) { // token 过期或失效 localStorage.removeItem('token'); store.commit('clearUserInfo'); router.push('/login'); } ElMessage.error(res.msg || '请求失败'); return Promise.reject(new Error(res.msg)); } return res; }, (error) => { if (error.response?.status === 401) { localStorage.removeItem('token'); store.commit('clearUserInfo'); router.push('/login'); } ElMessage.error('网络异常,请稍后重试'); return Promise.reject(error); } );4.4 部署到云服务器的经验
课设如果要求上线演示,一般是买一台最便宜的云服务器(2核4G 就远远够用了)。部署的核心思路:
后端打成 jar 包,mvn package后在target目录下拿到pet-adoption-0.0.1.jar。上传到服务器,直接nohup java -jar pet-adoption.jar > app.log 2>&1 &运行。前端项目先npm run build,生成的dist目录上传到服务器。然后在服务器上装 Nginx,配置如下:
server { listen 80; server_name 你的域名或IP; # 前端静态文件 root /usr/share/nginx/html; index index.html; # 解决前端路由的 history 模式刷新404问题 location / { try_files $uri $uri/ /index.html; } # 后端接口反向代理 location /api/ { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 图片访问 location /images/ { alias /usr/local/pet/images/; } }这套部署下来,就能通过域名或 IP 直接访问系统了。花小钱把整套流程走完,你的项目完整度、对系统的掌控能力,在答辩时一开口就能听出来。
5. 常见问题深层排查与新思路拓展
5.1 排查问题的方法论
很多同学遇到 Bug 第一反应是重新跑一遍,或者说“刚才还好好的”,这种心态对调试没有任何帮助。我强烈建议掌握最基础的 Debug 三步法:第一,看后端控制台日志,是否有异常堆栈,定位到具体代码行。第二,看前端浏览器控制台和网络面板,请求是否发出,响应体是什么,状态码是多少。第三,直接进入 MySQL 命令行或数据库可视化工具,查数据最新状态是否符合预期。把这三个层面的信息拼在一起,十有八九能定位问题。
比如前端提交申请后按钮没反应,前端没报错,后端控制台却打印了SQLSyntaxErrorException,那八成是数据表字段名和实体类属性名对不上,或者 SQL 里用了 MySQL 的保留字(比如apply在某些版本就是保留字)。把 SQL 取出来粘到数据库工具里跑一遍,马上真相大白。
5.2 如何给项目加一点“亮点功能”
如果基础功能顺畅了,时间还有富余,可以考虑在低成本的前提下加一点功能性亮点,让项目在同类作品里更突出。
一个是“宠物健康档案”功能。给每只宠物增加疫苗接种、驱虫记录的简单时间轴展示,数据存进一张pet_health_record表,前端在详情页用一个时间线组件渲染出来。虽然逻辑还是 CRUD,但在领域故事上很有说服力。
另一个是“领养人黑名单”能力。管理员在审核失败时如果勾选了“标记黑名单”,则该用户后续的申请在提交时就被后端拦截。这个功能可以体现你对业务规则复杂度的思考,也适合用来做多角色权限控制的进阶展示。
还有一种是引入定时任务,比如每天扫描超过 7 天仍处于“申请中”的宠物,如果没有新进展,自动提醒管理员回访处理。SpringBoot 的@Scheduled注解就能搞定,但对课设来说稍微加分。
5.3 项目结构演进与“简历项目”包装建议
如果你打算把宠物领养系统作为简历上的项目经历,不要只写“开发了宠物领养系统”。建议按标准格式撰写:
第一句写项目背景和业务目标,比如“为了改善流浪动物信息不对称问题,构建了一套基于前后端分离的在线领养平台,覆盖宠物信息管理、领养流程审核、互动反馈等闭环流程”。第二句写技术栈,“后端采用 SpringBoot + MyBatis Plus,数据库使用 MySQL,认证授权采用 JWT 方案,前端基于 Vue + Element UI 构建组件化页面”。第三句写个人职责和亮点,“独立完成数据库表设计、核心领养状态机逻辑开发以及前后端联调测试,解决了多条件筛选查询性能和重复申请数据一致性等问题”。最后一句写项目成果,“系统已在某高校毕业设计展中演示,日均模拟访问量 XXX 次(这里如实写即可,没数据就不写)”。
不要虚构夸张的数据,但要把真实做过的事情讲到位,特别是状态流转设计和事务处理这类隐含技术细节的内容,面试官很容易顺着细节往下问。
5.4 时间规划与心态建议
最后聊点现实的,毕竟我见过太多学生在最后两周才醒悟要写系统。如果你的毕设时间线很紧张,四月底要交初稿,现在才刚开始,我的建议是压缩计划:第一周完成数据库设计与后端基础接口(宠物 CRUD、分页查询、用户登录注册);第二周完成前端列表页、详情页、登录注册页以及联调测试;第三周补齐后台管理端(宠物审核、领养审核)并部署到服务器。每天保持至少三个小时的连续编码时间,不要东一榔头西一棒子。
如果是已经写完了基础版本,准备打磨成精品课程设计,那就把重心从“新增功能”转移到“稳定性和交互细节”上。把按钮 loading 状态补齐,把表单校验提示统一,把控制台警告清零,把接口失败提示做得更友好——这些打磨工作对整体印象的提升,不亚于多写两个功能模块。
根据我自己的项目经验,这类两段式系统最怕的不是代码复杂,而是调试时前后端两头没头绪。一个实用的做法是:前期前端用一个简陋的页面把主要接口全部测通,再去美化界面。接口通了,后面的一切都只是填充,不慌。
希望这篇实战拆解能让你少走一些弯路。B站上虽然有不少教学视频,但大部分都在念代码,很少有人把“为什么这样做”讲明白。如果你在动手过程中踩到了本文没覆盖到的坑,大概率是同一种问题在不同配置环境下表现不一样。检查一下 JDK 版本、MySQL 版本,再不行就重置数据库重新跑一遍初始化脚本——多数问题出在脏数据上,而不是代码逻辑上。