首页/新闻资讯/正文详情

SpringBoot+Vue前后端分离实战:宠物管理系统从0到1完整解析

发布时间:2026/9/26 4:34:39 来源:云帆数科 栏目:资讯中心
SpringBoot+Vue前后端分离实战:宠物管理系统从0到1完整解析
做了这么多期开发相关的复盘后台时不时有人问“有没有一个SpringBoot和Vue都没玩熟也能照着抄的完整项目”我基本都会推荐宠物管理系统。不是偷懒是这类题材实在太适合入门到进阶的过渡了。你把它拆开看用户登录、权限控制、增删改查、文件上传、前端路由守卫、接口联调、打包部署前后端分离开发里能遇到的典型问题基本上它都能覆盖到。而且宠物领养这个业务场景自带“演示友好”属性做出来的东西拿去给家里人或朋友看一眼就知道是干嘛的不像那种抽象的进销存系统讲了半天对方还是面无表情。这篇文章不走那种“先贴一个需求文档再贴一堆代码”的路子我更想聊清楚每个设计决策背后的原因。比如为什么要用JWT而不是Session、为什么数据库里要设计状态机字段、为什么前端要封装axios实例、为什么Vue项目打包后刷新会404……这些问题你只看代码是学不到的但踩过一次坑之后会记一辈子。下面这些内容完全基于我自己实际把这个项目从0到1做出来的过程包括中间踩过的坑适合正在做同类项目、准备Java全栈毕设、或者刚入门前后端分离开发的朋友当作一份完整参考。1. 项目整体设计与技术选型思路1.1 需求边界宠物管理系统到底要管什么很多同学在选题的时候会把“宠物管理系统”想得特别大上来就想做在线问诊、宠物保险、智能喂食器联动结果做两星期连登录都没写完。我建议第一版把边界收紧只做最核心的三块宠物信息管理、领养流程管理、健康档案管理再加一个公告通知模块作为补充。这套需求组合覆盖了典型的B端管理后台加C端浏览场景演示效果也完整。具体到功能可以这样拆普通用户端注册登录、浏览宠物列表、看宠物详情、提交领养申请、查看我的申请进度、查看公告、维护个人资料。管理员端登录、宠物增删改查含上下架审核、领养申请审核通过/拒绝、健康档案录入疫苗、驱虫、体检记录、公告发布、用户列表管理。这里有个细节值得注意很多同类项目只做“管理员录入宠物、用户只能看”但那样演示起来非常单薄因为你很难向听众解释“为什么我要用这个系统”。加上领养申请和审核流程之后整个系统就有了一个“业务闭环”答辩或者面试的时候可以从数据流的角度把故事讲完整用户看到宠物→提交申请→管理员审核→状态流转→健康档案补充一眼就能看出你做过整体设计。1.2 为什么这一套技术栈值得写技术选型这块我先说结论SpringBoot 2.7.x Vue3 Vite MyBatis Plus MySQL 8是目前最适合复现、资料最全、也最贴近中小型项目实际形态的组合。很多人纠结要不要上SpringBoot 3.x。我的建议是除非你想刻意体验新特性否则暂时不用。SpringBoot 3.x要求JDK17起步很多学校的教学环境和旧机器上跑起来麻烦而且网上海量教程、面试题、毕设模板都是基于2.x的遇到问题搜索成本低很多。2.7.18是2.x家族最后一个维护版本完全够用。JDK我用的是8不是因为它先进恰恰是因为它“老得让人放心”公司里还有大量存量项目跑在JDK8上。前端这边强烈推荐直接学Vue3。Vue2虽然很多老项目还在用但已经停止维护了。Vue3 Vite的启动速度比Webpack那种动辄几十秒的冷启动舒服太多。UI库我选了Element Plus它和Element UI一脉相承组件丰富后台管理界面套上它基本不怎么需要写CSS。数据持久层用MyBatis Plus而不裸写MyBatis道理很简单CRUD的样板代码真的不值得反复手写。MyBatis Plus内置BaseMapper单表增删改查一行代码不用写SQL分页插件更是开箱即用。后面我会单独讲分页插件启动失效这个坑。1.3 整体架构和目录规划前后端分离的结构很清晰pet-manage-backend # 后端工程 ├── src/main/java/com/pet │ ├── controller # 接口层 │ ├── service # 业务逻辑层 │ ├── mapper # 数据访问层 │ ├── entity # 数据库实体 │ ├── dto # 请求/响应对象 │ ├── config # 配置类跨域、拦截器、静态资源映射 │ ├── common # 统一返回体、异常、工具类 │ └── PetApplication.java └── src/main/resources ├── application.yml └── mapper # XML文件复杂SQL用 pet-manage-frontend # 前端工程 ├── src │ ├── api # 接口请求封装 │ ├── router # 路由配置 │ ├── stores # Pinia状态管理 │ ├── views # 页面组件 │ ├── components # 公共组件 │ └── main.js └── vite.config.js这个结构没什么特别的但特别重要。我见过有人把所有类全扔在同一个包下面几百个文件看着就头痛。包结构清晰不仅是给别人看的更是给三周后的自己看的——你肯定不想为一个字段命名问题在一堆文件里翻了二十分钟。2. 后端核心实现与关键细节2.1 统一返回体和全局异常处理先做好动手写业务接口之前我强烈建议先把“基建”搭好。前后端分离项目里前后端必须约定一个统一的响应格式否则联调的时候能吵起来。我用的返回体结构很简单public class ApiResponseT { private Integer code; // 业务状态码200表示成功 private String message; // 提示信息 private T data; // 业务数据 }配合一个枚举来管理状态码public enum ResultCode { SUCCESS(200, 操作成功), ERROR(500, 系统异常), UNAUTHORIZED(401, 未登录或登录已过期), FORBIDDEN(403, 没有权限), PARAM_ERROR(400, 参数错误); }写接口的时候Controller直接return ApiResponse.success(data)出错时配合全局异常处理器代码体积能缩一半。有些朋友图省事让接口直接返回一个Map或者裸对象前端取值时一会data.code一会res.data很快就会乱套。全局异常处理器用RestControllerAdvice实现把业务异常、参数校验异常、兜底异常分别处理。举个我实际遇到的例子前端传参类型不对时SpringBoot默认返回的报错信息是英文的前端弹窗直接弹英文非常不友好。我在全局异常处理器里统一catch了MethodArgumentTypeMismatchException、BindException这类异常转成中文提示体验立刻不一样。2.2 为什么不选Session而用JWT做登录认证宠物管理系统里有两个端用户端和管理端如果还按老一套用Session登录会遇到几个头疼的问题跨域时Cookie处理麻烦、分布式环境Session共享要另外引入Redis、前后端分离场景下Postman调试也不舒服。所以我选了JWT。JWTJSON Web Token本质上就是一个签了名的JSON字符串分成Header、Payload、Signature三部分。登录成功之后后端生成一个token返回给前端前端存在localStorage里之后每次请求在请求头加上Authorization: Bearer token。后端通过拦截器解析token就知道当前请求是谁、有没有权限。这个模式是现在前后端分离项目的主流做法。// 生成token String token Jwts.builder() .setSubject(userId.toString()) .claim(role, user.getRole()) .setExpiration(new Date(System.currentTimeMillis() 3600_000L)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); // 解析token Claims claims Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token) .getBody();jwt库我用的jjwt 0.9.1。这里提醒一下这个版本的API到0.11.x之后有了变化很多网上教程代码不能直接复制你如果用的新版本记得看官方文档。拦截器的注册也很重要。我写了两个自定义注解RequireLogin和RequireRole(ADMIN)分别用在需要登录的接口和只放行管理员的接口上。然后在WebMvcConfig里配置拦截器拦截所有请求在preHandle里面读取注解配合HandlerMethod实现“被注解标注才做登录校验”。这样做的好处是接口默认不鉴权只有标注了的才校验避免新人忘了加注解导致接口裸奔。2.3 宠物列表分页查询MyBatis Plus的正确姿势宠物列表是系统里访问最频繁的接口必须要分页。MyBatis Plus有现成的分页插件但有个坑分页插件需要单独配置不配置的话分页不生效。这个坑我身边不下三个人踩过。配置如下Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }分页查询的代码非常简洁PagePet page new Page(pageNum, pageSize); LambdaQueryWrapperPet wrapper new LambdaQueryWrapper(); wrapper.eq(pet - pet.getStatus() PetStatus.ON_SALE) .like(StringUtils.hasText(keyword), Pet::getName, keyword) .orderByDesc(Pet::getCreateTime); PagePet result petMapper.selectPage(page, wrapper);注意LambdaQueryWrapper里那个布尔条件的用法like(condition, column, value)的第一个参数为false时这个条件会被自动忽略。这样用户不填关键词、不选品种时不需要写N个if判断去组装条件代码干净很多。分页结果我建议封装成PageResult对象包含total、pageNum、pageSize、records这四个字段前端拿到之后直接渲染表格、计算总页数就完事了。2.4 文件上传与静态资源映射这里全是细节宠物系统一定会涉及图片上传。我的方案是上传的文件保存到本地的固定磁盘目录然后把访问路径存进数据库的pet表。重点有两个一是上传配置二是访问映射。application.yml里的配置spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MB上传接口的核心逻辑PostMapping(/upload) public ApiResponseString upload(RequestParam(file) MultipartFile file) { String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String newFileName UUID.randomUUID().toString().replace(-, ) ext; // 按日期分目录避免一个目录文件太多 String datePath LocalDate.now().format(DateTimeFormatter.ofPattern(yyyy/MM/dd)); File dir new File(uploadPath / datePath); if (!dir.exists()) dir.mkdirs(); file.transferTo(new File(dir, newFileName)); return ApiResponse.success(/upload/ datePath / newFileName); }静态资源映射配置Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(file: uploadPath /); } }这个配置里有个常见毛病file:后面如果没有正确拼上绝对路径访问图片会404。建议uploadPath用绝对路径不要用相对路径因为jar包启动时相对路径的工作目录不一定是你以为的地方。另外Windows和Linux的路径分隔符不一样直接用File.separator或者让框架处理别硬编码/。2.5 过滤器里顺手处理一次XSS与上传安全热搜词里有人搜SpringBoot全局过滤器处理XSS这个确实值得提一下。宠物系统里用户会提交姓名、申请备注之类的文本如果不做过滤别人在输入框里塞一段scriptalert(1)/script存进数据库再渲染回页面就能执行脚本。这就是典型的存储型XSS攻击。我的做法不太复杂但也够用写一个过滤器把请求体包装成可重复读的流然后对JSON里的字符串字段做HTML转义转lt;等再用Wrapper覆盖原来的getParameter和getInputStream。核心代码如下public class XssFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { chain.doFilter(new XssWrapper((HttpServletRequest) request), response); } }不过要提醒一点加了XSS过滤之后如果管理员发布公告时需要填写带HTML的富文本正文会被转义掉导致排版丢失。所以我的过滤策略是默认过滤但注解SkipXssFilter标记的接口放行比如公告正文的保存接口。安全性和业务要用“例外列表”来平衡而不是一刀切。在3.x的SpringBoot里这个过滤器直接注册一个FilterRegistrationBean即可注意过滤范围不要配成拦截所有包括静态资源否则会影响性能。3. 前端Vue项目的搭建与业务对接3.1 从环境到工程的初始化流程前端这块的第一关往往是环境。Vue3 Vite要求Node.js版本在16以上很多人卡在npm create vite报错多半就是Node太老。装Node我比较推荐用nvm而不是直接去官网装安装包因为nvm可以随时切换版本不同项目对Node版本要求不一样这个经验折腾过一次你就懂了。创建项目的命令npm create vitelatest pet-manage-frontend -- --template vue cd pet-manage-frontend npm install装完基础模板之后再补两个关键依赖npm install vue-router4 pinia axios element-plusElement Plus如果嫌全部引入包体积大可以用按需导入。但如果这是个中小型后台项目我建议直接全量引入省事。等真正做起来了再考虑优化首屏没必要一上来就给自己增加心智负担。3.2 路由设计静态路由加权限控制宠物系统按角色分两个入口所以路由设计我用了“基础路由 动态路由”的组合方式。基础路由包含登录页、注册页、宠物浏览列表页、宠物详情页这些所有用户都能访问的页面。后台管理相关的路由宠物管理、申请审核、公告管理等则通过meta.role标记为ADMIN在登录后根据用户角色动态添加。const router createRouter({ history: createWebHistory(), routes: [ { path: /login, component: Login }, { path: /pet/list, component: PetList }, { path: /pet/detail/:id, component: PetDetail }, { path: /admin, component: AdminLayout, meta: { role: ADMIN }, children: [ { path: pet-manage, component: PetManage }, { path: apply-manage, component: ApplyManage } ] } ] });全局前置守卫用来判断登录态和角色router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.role !isAdmin(token)) { next(/login); } else { next(); } });注意一个细节createWebHistory模式HTML5 History模式在部署到nginx之后如果你直接访问/admin/pet-manage这个地址会404因为nginx找不到这个物理路径。解决方式是在nginx配置里加一句try_files $uri $uri/ /index.html;把请求fallback到前端的入口文件。这个坑我放到后面的部署环节再展开一次因为太容易漏了。3.3 axios封装拦截器里藏了多少细节axios如果不做封装每个页面都要重复写baseURL、塞token、处理错误码过几天你想改一个状态码的判定逻辑得去十几个文件里改。我的做法是创建一个统一的axios实例import axios from axios; import { ElMessage } from element-plus; import router from /router; const service axios.create({ baseURL: /api, timeout: 15000 }); // 请求拦截器统一塞token service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer token; } return config; }); // 响应拦截器统一处理业务状态码 service.interceptors.response.use( response { const res response.data; if (res.code 200) { return res.data; // 直接返回业务数据页面里就不用再取res.data.data } else if (res.code 401) { router.push(/login); } else { ElMessage.error(res.message); return Promise.reject(new Error(res.message)); } }, error { ElMessage.error(网络异常请稍后重试); return Promise.reject(error); } );有个比较容易被忽略的点我在成功回调里直接return了res.data而不是整个响应对象。这样页面里调用getPetList()拿到的直接就是列表数据根本不需要再写const res await getPetList(); res.data.records这种二层取值代码干净不少。3.4 宠物列表页和数据对接的细节实现宠物列表页是前台的门面我用Element Plus的Card组件做成卡片栅格。核心的关注点是分页参数怎么传入、加载状态怎么控制、空数据怎么展示。template div classpet-grid el-empty v-ifloading false list.length 0 description暂无宠物 / el-row :gutter20 el-col :span8 v-forpet in list :keypet.id el-card el-image :srcpet.imageUrl fitcover / h3{{ pet.name }}/h3 p{{ pet.breed }} · {{ pet.age }}岁/p el-button clickgoDetail(pet.id)查看详情/el-button /el-card /el-col /el-row el-pagination v-model:current-pagequery.pageNum v-model:page-sizequery.pageSize layoutprev, pager, next :totaltotal / /div /template在script里查询参数的响应式定义用reactive分页数据变更后重新请求。这里有个细节el-pagination的current-page改变时应该用一个watch去触发请求或者在current-change事件里调用我常用watch(() query.pageNum, fetchList)。避免在模板里直接调用方法导致重复请求。宠物详情页的id从路由参数拿route.params.id。录入领养申请时把宠物id一起提交给后端后端就能知道用户对哪只宠物发起了申请。另外图片地址如果直接以相对路径/upload/2024/05/xx.jpg存储在数据库得在模板里拼接完整地址通常用computed或者utils函数统一处理。4. 数据库设计与业务状态流转4.1 表结构清单与字段选型我设计表的原则是“够用且可扩展”不过度设计但也不预埋太多用不到的字段。核心表一共五张表名作用关键字段user用户和员工账号id, username, password, role, nickname, phonepet宠物信息id, name, breed, age, gender, image_url, status, description, create_timeadoption_apply领养申请id, user_id, pet_id, apply_reason, status, create_time, update_timepet_health_record健康档案id, pet_id, record_type, content, record_date, record_timeannouncement公告id, title, content, create_time你可能会问为什么把健康档案独立成表而不是直接往pet表里塞几个字段。因为一只宠物的疫苗记录、驱虫记录、体检记录都是多条数据一条一条往一行里塞显然不现实。独立成表之后宠物详情页可以随时查它的“健康时间线”以后要扩展成宠物医院的那种频次记录、提醒下次疫苗时间也都方便。4.2 几张核心表的设计细节user表里password字段我建议用varchar(100)而不要用varchar(32)因为BCrypt加密后的密码字符串长度超过32很多人的MD5习惯还停留在32位。数据库里我直接存BCrypt哈希不存明文。pet表的status字段我定义为tinyint用0、1、2分别表示“待上架、在售、已领养”。这里就引出状态枚举的问题了Java实体里我建一个PetStatus枚举数据库存int通过MyBatis Plus的枚举自动映射保证代码里不会出现if (pet.getStatus() 2)这种魔法值。adoption_apply表是业务核心它的status字段用四个值0待审核、1已通过、2已拒绝、3已完成领养。最开始我只设计了0和1后来发现没有“申请人与宠物最终完成交接”的状态整个流程看起来不闭环补上“已完成”状态后宠物从在售→被申请→通过→完成领养的链路就顺了。4.3 状态流转如何避免脏数据领养申请的状态流转不是一个随意的更新它有明确的方向。比如“待审核”可以到“通过”或“拒绝”但“已拒绝”不能直接跳到“已完成”。如果只写一个updateStatus(applyId, newStatus)很容易出现逻辑错误。我用Service层做了一层状态校验每次修改状态时先查当前状态再判断是否允许迁移public void auditApply(Long applyId, Integer result) { AdoptionApply apply getById(applyId); if (apply.getStatus() ! 0) { throw new BusinessException(该申请已处理请勿重复操作); } apply.setStatus(result 1 ? 1 : 2); updateById(apply); }加这个判断的收益是前端连续点两次“通过”按钮或者两个管理员同时操作一条申请时不会把状态搞成不可恢复的脏数据。系统虽小“状态机”的思想值得养成。4.4 索引设计与查询优化宠物列表页最常见的筛选条件品种、状态、关键字。我在pet表上建了(status, create_time)的联合索引因为列表查询基本都是WHERE status ? ORDER BY create_time DESC。nascent的like查询如果放在name字段上全表扫描是难以避免的但数据量小的时候问题不大。我实际测试过几千条数据的表不建索引也没什么感觉。但写项目的目的不只是跑通而是明白什么时候该建索引。所以我在代码注释里把每个索引的创建理由写清楚了这样答辩或Review时别人问起来你也能讲出依据。5. 前后端联调与部署实战5.1 开发环境的跨域代理配置前后端分离开发时Vue跑在5173端口后端跑在8080端口直接请求必然跨域。两个解决办法一个是后端开CORS一个是用Vite的proxy代理。我在开发环境用Vite proxy原因是不用污染后端代码生产环境再用nginx反向代理最终整个系统对外只有一个域名。vite.config.js中配置server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端请求/api/pet/list开发服务器把请求转发到http://localhost:8080/api/pet/list浏览器视角就是同源跨域问题直接就没了。后端那个CORS配置可以留着但开发环境用代理更干净。5.2 生产环境打包与nginx部署先讲后端。用Maven打jar包mvn clean package -DskipTests然后直接java -jar pet-manage-backend.jar --server.port8080如果生产环境是Docker我提供一个精简化Dockerfile参考FROM openjdk:8-jre-alpine COPY target/pet-manage-backend.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, /app.jar]很多人在Docker部署上踩坑的点是时区和内存参数。建议在启动命令里加上-Duser.timezoneAsia/Shanghai和-Xms256m -Xmx512m写进ENTRYPOINT里。另外Docker容器里上传的文件如果放在容器内容器重启文件就丢了正确做法是用-v /data/upload:/data/upload把上传目录挂载到宿主机。前端构建npm run build生成的dist目录交给nginx托管。nginx配置要点有两个一是try_files要加解决history模式刷新404二是把/api请求转发到后端服务。示例配置server { listen 80; server_name your-domain.com; root /opt/pet-frontend/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /upload/ { proxy_pass http://127.0.0.1:8080; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }注意proxy_pass http://127.0.0.1:8080;后面要不要带路径的问题。如果location是/api/proxy_pass不带路径表示把完整的/api/xxx转发过去如果proxy_pass写成http://127.0.0.1:8080/则会把/api/前缀去掉再转发。这里的差异很容易把人搞晕我建议统一用“不带路径”的写法前后端接口路径保持完整。5.3 上传大小限制的连环坑这个坑真的太典型了。前端上传图片时如果文件超过10MB后端会抛异常但问题是你得在三个地方同时放开限制才生效一是SpringBoot的spring.servlet.multipart.max-file-size二是max-request-size三是nginx的client_max_body_size。很多人配了前两个忘了nginx默认1MB的限制结果开发环境好好的部署到服务器上一上传大图就413全是一环扣一环。我遇到过更隐蔽的情况nginx加了client_max_body_size配置后直接nginx -s reload不生效必须重启。因为该配置项在http块/服务器块加载时读取reload对某些场景不生效。后来我都用nginx -t nginx -s reload来验证稳妥。6. 常见问题与排查技巧实录6.1 问题速查表下面这个表是我做项目过程中遇到频率最高的几个问题每一条都可以单独拿出来当面试题讲。现象原因解决方案前端请求接口报跨域开发环境未走代理 / 后端CORS配错用Vite proxy确认allowedOrigins不是*且allowCredentials为true时浏览器要求精确域名分页数据一直返回全部MyBatis Plus分页插件没配置在配置类中注入PaginationInnerInterceptor上传图片后页面404静态资源映射路径没配对确保addResourceHandler和file:绝对路径正确上传大文件报413nginx client_max_body_size小于后端限制同步调大nginx限制并重启前端拿到的id科学计数法Long类型精度丢失后端将Long序列化为字符串刷新页面404history模式未配置fallbacknginx添加try_files $uri $uri/ /index.html;日期字段前端变成一串数字LocalDateTime序列化格式问题配置Jackson统一格式yyyy-MM-dd HH:mm:ss登录后某些接口仍返回401拦截器未放行OPTIONS预检请求拦截器中对OPTIONS请求直接放行6.2 三个值得展开讲的经典caseLong类型精度丢失。这个坑很重要。MyBatis Plus默认的主键策略是ASSIGN_ID生成的是雪花ID这个ID是一个Long型长度19位。JavaScript的Number类型只能安全表示到2的53次方约等于9007199254740992也就是16位。所以后端返回给前端的长ID到前端就丢了精度。解决办法是在Jackson配置里指定Long和long都序列化为字符串Bean public Jackson2ObjectMapperBuilderCustomizer longToStringCustomizer() { return builder - builder .serializerByType(Long.TYPE, ToStringSerializer.instance) .serializerByType(Long.class, ToStringSerializer.instance); }401拦截的处理顺序。在axios响应拦截器里401对应两种情况一是真的没登录二是token过期。我遇到过一个bug登录页本身调用了“获取验证码”接口这个接口是不需要token的但请求拦截器里没判断url硬塞了一个过期token进去后端拦截器收到过期token直接返回401前端响应拦截器一看401就跳登录页结果用户连验证码都刷不出来直接死循环。解决方式是在请求拦截器里加白名单判断登录页相关接口不塞token。日期字段序列化。Java 8的LocalDateTime默认Jackson序列化出来是一串数字时间戳前端拿到之后哪怕转成Date对象显示格式也是乱的。我在application.yml里统一配置spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai注意spring.jackson.date-format对LocalDateTime不生效它只管java.util.Date。LocalDateTime要生效必须引入jackson-datatype-jsr310模块SpringBoot已自动引入并且用JsonFormat或者自定义ObjectMapper。我的做法是自定义一个Jackson配置类给LocalDateTime、LocalDate统一注册格式化器。另外time-zone: Asia/Shanghai一定要配否则服务器是UTC时区时存的日期会和本地差8小时。6.3 从根上避免问题的几个习惯想省掉后面排查的功夫写代码的时候就该养成好习惯。第一所有接口的入参都写DTO不要直接拿Entity当请求体。Entity和数据库耦合太紧字段改了会影响接口兼容性。第二凡是有状态字段的表一律用枚举不在代码里散落魔法值。第三日志打全拦截器里记录请求路径、用户id、耗时出问题能快速定位。第四Mock数据提前准备好联调时不用临时去数据库造数我写了一个CommandLineRunner启动时如果检测到数据库是空的就自动插入一批宠物数据演示的时候直接就能看效果。7. 扩展方向这个系统还能怎么玩出花来一个基础版宠物管理系统做完之后如果你想让它更有竞争力有几个方向性价比很高。第一个方向是加入“宠物档案时间轴”把疫苗、驱虫、体检记录按时间轴展示这是健康档案模块的自然延伸后端只需要把查询按时间排序前端用Vue的时间轴组件渲染即可工作量不大但视觉效果很加分。第二个方向是加一个站内消息通知。用户提交领养申请后管理员审核通过时给用户发一条站内消息用户在前端消息中心查看。这块可以自己写一张message表也可以引入WebSocket做实时推送看你的目标层级。自己做一张表是最稳的WebSocket虽然炫酷但引入的技术复杂度一下子上升不少。第三个方向是统计看板。管理员首页展示宠物总数、在售数量、领养成功数、近一周新增申请数这些指标配合ECharts画几个饼图柱状图作为毕设或简历项目视觉效果直接上一个档次。第四个方向是考虑性能优化。如果宠物图片多可以接入对象存储而不是本地磁盘存储如果查询量上来了可以给列表接口加Redis缓存缓存热门宠物数据。这些优化的思路比实现本身更值得写进项目文档里面试官问起的时候你有话可说。我个人认为这个系统的价值不在于“宠物”两个字而在于“SpringBootVue前后端分离”这条主线。你把它换成校园二手交易、社区活动报名、企业资产登记核心逻辑和架构几乎可以平移。所以做这个项目的时候重点不是把它背下来而是每一行代码、每一个决策都搞清楚为什么这么写这才是它真正能留给你的东西。最后分享一个我做这套系统时实际用到的技巧早期联调阶段后端接口还没有完全写完的时候前端不要干等。用json-server或者直接把mock数据放到本地,先把页面画出来、把路由跑通、把交互捋顺然后后端接口写好了再替换成真实的axios调用。这种“前后端并行开发”的节奏才是前后端分离模式真正的优势也是你从写单页demo走向真实项目时最该建立的工作习惯。这套项目做完之后你对SpringBoot的配置、Vue的组件通信、路由守卫、axios拦截、数据库设计、nginx部署这些环节会有一个完整且扎实的认知这种“整链路打通”的感觉是刷多少道面试题都换不来的。

相关推荐

快递包装盒缺陷检测数据集实战:1200张图跑通YOLO训练与避坑指南
快递包装盒缺陷检测数据集实战:1200张图跑通YOLO训练与避坑指南

简介:这份资源面向计算机视觉初学者与目标检测实践者,提供一套可直接用于YOLO系列算法训练的快递包裹及包装盒缺陷检测数据集,帮助解决工业质检、物流分拣等场景下样本获取困难的问题。压缩包共约2000个文件,以1201个txt标签文件、… · 2026/9/26 4:34:39

WinForms实现带CheckBox的多选下拉框:两种方案详解
WinForms实现带CheckBox的多选下拉框:两种方案详解

简介:面向Windows桌面应用开发者的自定义控件示例,主要演示如何在ComboBox中集成CheckBox控件,实现单项下拉框向多选列表的扩展,解决标准组合框仅能单选的问题。资源为MultiSelectComboBox Demo(Codebehind)的完整工程&#xff0c… · 2026/9/26 4:34:32

IP、DNS、TCP、NAT:一次外网访问故障背后的网络基础全解
IP、DNS、TCP、NAT:一次外网访问故障背后的网络基础全解

家里的NAS装了半个月,内网访问一直好好的,朋友说“用手机流量打不开”,我第一反应是“路由器端口映射没配好”,结果折腾了两天,最后发现不是映射的问题,而是我对“IP、网关、DNS、TCP”这些最基础的网络概念… · 2026/9/26 4:34:32

MiniMax H3 视频生成工作流:从节点逻辑到显存优化的完整实战指南
MiniMax H3 视频生成工作流:从节点逻辑到显存优化的完整实战指南

很多新手第一次接触到 MiniMax H3 的 ComfyUI 工作流时,都会经历一个比较尴尬的阶段:手里拿着别人分享的 workflow JSON,拖进界面一看满屏红色报错,要么模型加载失败,要么跑完生成出来是黑屏,要么干脆直接爆… · 2026/9/26 5:48:42

多平台AI大模型API统一调用层搭建实战:OneAPI与LiteLLM选型及避坑指南
多平台AI大模型API统一调用层搭建实战:OneAPI与LiteLLM选型及避坑指南

1. 从一堆API Key到统一入口:我为什么要折腾这件事手里攒了七八个平台的API Key,每个平台的接口格式、鉴权方式、返回结构都不一样,这件事本身就够让人头疼的。更别说有些平台今天能用明天就限流,有些模型跑着跑着突然告诉你余额不… · 2026/9/26 5:48:42

万象生鲜系统:首衡集采集配减碳激励适配政策
万象生鲜系统:首衡集采集配减碳激励适配政策

万象生鲜系统是生鲜配送系统中的杰出代表,涵盖订单处理、采购管理、库存监控等功能模块。具有高度智能化、用户体验好等特点万象生鲜系统的新政策聚焦集采集配的减碳激励,旨在让商家在日常运营中更好落地绿色采购。该举措不仅提供财政激励,还… · 2026/9/26 5:48:42

扣子COZE AI编程实战:工作流编排与智能体落地指南
扣子COZE AI编程实战:工作流编排与智能体落地指南

简介:COZE AI编程案例是一份面向开发者的实操型编程资料,聚焦智能机器人开发场景,涵盖开发实战案例、生产力工具技巧、API集成方案与新手入门指南。资源以PDF文档形式呈现,共1个文件,压缩包大小186KB,便于快… · 2026/9/26 5:48:36

LTE网优实战:华为设备L3层信令分析与KPI优化指南
LTE网优实战:华为设备L3层信令分析与KPI优化指南

简介:这份面向通信工程师、网络优化人员及备考华为L3认证的LTE网优学习文档,系统梳理LTE网络优化中的高频考点与易错知识点。内容涵盖UE移动对传播时延的影响、PCI Mod3冲突对RS-SINR的恶化、MBSFN参考信号端口、VOLTE与CSFB改造量、PRACH Format3在UpPT… · 2026/9/26 5:48:35

递归自我改进RSI落地指南:数据、工具、结构三面与五条定律
递归自我改进RSI落地指南:数据、工具、结构三面与五条定律

1. 从“RSI”这个词说起:它到底指什么先把话说在前头,RSI 这三个字母在不同圈子里指向完全不同的东西。做交易的朋友第一反应是相对强弱指标,做工程的朋友可能想到的是信号完整性,但最近一段时间在技术社区里被反复讨论的 RSI&… · 2026/9/26 5:48:35

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码