SpringBoot+Vue前后端分离实战:同城宠物照看系统从0到上线全解析

📅 发布时间:2026/10/17 12:52:51
SpringBoot+Vue前后端分离实战:同城宠物照看系统从0到上线全解析
说出来可能有点老生常谈但我这两年接的私活和带新人做的项目里SpringBoot Vue 的前后端分离项目确实是需求量最大的一类尤其是带业务闭环、有明确角色分工的系统。就拿“同城宠物照看系统”来说它不像商城、博客那样满大街都是但又覆盖了用户认证、订单流转、信息匹配、评价体系这些核心模块作为实战项目或者毕设性价比非常高。这篇内容我打算完完整整地拆一遍整个项目的落地过程从业务模型到表结构从后端接口到前端页面再到本地调试和部署时容易踩的坑。不管你是打算拿它做课程设计还是想自己接单搞个副业项目又或者是单纯想搞懂前后端分离项目的完整链路这篇应该都能让你少走不少弯路。1. 宠物照看需求到底长什么样业务模型拆解很多人一上来就急着写代码结果做到一半发现订单状态理不清、用户角色分不明代码改得一团糟。做这个系统之前我建议大家先花半天时间把业务模型老老实实捋一遍。1.1 同城宠物照看的核心痛点与实际场景传统宠物照看无非三个渠道宠物店寄养、亲戚朋友帮忙、上门喂养。但这三条路各有各的糟心事。宠物店寄养价格高而且很多宠物换个环境容易应激尤其是猫换个地方能不吃不喝好几天找亲戚朋友帮忙欠人情不说对方不一定有养宠经验真出了事责任都说不清楚上门喂养倒是灵活但你怎么确认对方靠谱同城宠物照看系统解决的就是这个信息匹配和信任背书的问题。它的核心业务逻辑是这样的有照看需求的宠物主人在平台上发布需求包括宠物类型、服务时间、服务类型上门喂养、遛狗、短暂寄养等、预算范围而有空闲时间、喜欢宠物的照看人可以在平台上浏览这些需求选择合适的订单接单。接单之后双方线下对接服务完成之后宠物主人确认并支付最后双方互相评价积累信用。这个模型和滴滴打车的逻辑非常像核心就是需求发布、匹配接单、服务执行、确认结算、信用沉淀这五个环节。想清楚这条主链路后面所有表结构和接口设计都有了依据。1.2 三类用户角色与权限边界系统里我划分了三个角色宠物主人发布需求的人、照看人接单提供服务的人、系统管理员后台管理。宠物主人的核心操作是登录注册、维护宠物档案、发布照看需求、浏览照看人信息、确认订单完成、发起评价。照看人的核心操作是登录注册、浏览附近需求、申请接单、查看已接订单、确认服务完成、查看评价和收入。管理员的核心操作是用户管理封禁/解封、需求审核过滤违规内容、订单监督、数据统计。三个角色的操作边界一定要分清楚不然就会出现“宠物主人也能接单”这种逻辑事故。在代码层面我习惯用一张用户表加一个 role 字段来区分配合拦截器做接口级别的权限校验比单独建三张用户表省事得多逻辑也更清晰。1.3 业务闭环中的关键流程我大概列一下系统里最重要的三段流程这三段流程的设计直接决定了代码质量第一段是发布需求到接单。宠物主人创建订单订单状态为“待接单”照看人浏览到订单后点击“接单”订单状态变更为“已接单”同时宠物主人端会看到照看人的联系方式。第二段是服务执行到确认完成。双方线下对接完成后照看人在系统里点击“申请完成”宠物主人确认无误后点击“确认完成”订单状态变为“待评价”。这里有个细节如果照看人点击完成之后宠物主人一直不确认系统需要一个自动确认机制一般是24小时或者48小时自动确认我建议做成可配置项放后台。第三段是评价与信用沉淀。订单确认完成后双方可以互相评价评价内容包括打分比如1到5星和文字留言这些数据会汇总到双方的个人主页上作为后续交易的信任参考。想清楚这三段流程你的状态机就出来了后面写代码就是水到渠成的事。2. 技术栈选型复盘SpringBoot Vue这套组合为什么经久不衰技术上没什么花活主打一个稳定成熟。SpringBoot做后端Vue做前端MySQL存数据Redis做缓存和登录状态这套组合被验证过太多次了网上资料多、问题答案全、新人上手快不管是自己学习还是交给甲方都不容易翻车。2.1 后端核心组件与各自职责SpringBoot我用的2.7.x版本稳定且兼容性好。ORM层选了MyBatis-Plus说句实话单表CRUD用MyBatis-Plus真的是省了太多事内置的BaseMapper接口直接继承了selectById、insert、updateById这些常用方法连SQL都不用写复杂查询再用Select注解或者 XML 搞定。权限认证用的JWT具体实现是jjwt库。登录成功之后后端生成一个带过期时间的token返回给前端前端存到localStorage里之后每次请求在请求头带上Authorization: Bearer token后端用一个拦截器统一解析token并设置当前用户上下文。比起传统的Session方案JWT天然适合前后端分离后端不需要维护会话状态横向扩展的时候也舒服。Redis在这里干了三件事一是存验证码注册和登录的时候生成图形验证码或短信验证码5分钟过期天然适合Redis二是做热点数据的缓存比如首页的推荐需求列表不用每次都查数据库三是可以做接口限流防止有人恶意刷接口。不过这个项目里Redis不是必须的如果本地环境没装Redis会非常痛苦后面调试章节我会细说怎么处理。2.2 前端工程结构与开发环境前端我用的Vue 2 Element UIVue 3 Element Plus当然也行但Vue 2的资料存量实在太大遇到问题一搜能搜出几百篇靠谱答案新人上手成本更低。构建工具用的Vue CLINode版本建议14以上太低跑不起来。前端工程结构我习惯这样组织src/api目录按业务模块封装接口请求比如user.js、order.js、pet.jssrc/router目录路由配置包含动态路由和路由守卫src/store目录Vuex管理用户状态、登录态src/views目录页面组件比如首页、发布页、订单详情页、个人中心src/utils目录axios封装、token存取、公共工具函数配合vue.config.js里的 devServer 代理配置把/api开头的请求转发到后端的localhost:8080完美解决开发环境的跨域问题这点后面调试章节会详细说。2.3 前后端分离到底分的是什么很多同学对“前后端分离”的理解停留在“前端一个项目、后端一个项目”但真正的分离核心是数据接口契约。前端只管发请求、拿数据、渲染页面后端只管接收请求、处理逻辑、返回JSON。两者通过一份约定好的API文档或者Swagger在线文档进行协作。这个模式带来的直接好处是前端同学和后端同学可以并行开发不用互相等部署的时候前端静态文件扔到Nginx后端打jar包跑在服务器上互不干扰后期改版前端不影响后端逻辑反之亦然。当然代价也很明显联调阶段需要双方高度配合接口字段万一没对齐前端可能渲染出一片空白这个我在调试章节会展开讲一个真实的联调事故。3. 数据模型设计订单、宠物、评价三张表的联动逻辑表结构设计是数据库层面的地基地基歪了后面写代码能别扭死你。就这个项目而言我最终沉淀下来大概十来张表核心的几张我逐一给你讲明白。3.1 用户表与扩展信息表的设计思路用户表t_user是最基础的表核心字段包括主键id、手机号mobile唯一索引、密码passwordBCrypt加密存储、昵称nickname、头像avatar、角色role0管理员、1宠物主人、2照看人、城市city、状态status0正常、1封禁、创建时间create_time。然后我单独建了一张t_user_profile表用来存用户的扩展信息比如个人简介bio、服务价格service_price、服务类型service_type、接单次数order_count、好评率good_rate。为什么拆这张表因为列表页只需要用户表里的昵称和头像而照看人的详情页才需要扩展信息。如果把所有字段塞在一张表里列表查询会显得笨重拆分后查询性能更好代码也更清晰。这里有个实际业务冲突必须处理一个用户能不能既是宠物主人又是照看人我的立场是可以但主角色在注册时只能选一个后期可以在个人中心申请切换。对应到设计上就是用户表只存一个主角色扩展表里面加一个can_care字段标记是否有接单资质。这个问题在一开始如果没想清楚后端好几个接口的逻辑都会打架。3.2 宠物档案表非结构化信息的结构化存储宠物档案表t_pet是宠物主人发布需求前必须维护的数据。字段包括所属用户user_id、宠物名称name、宠物类型type0猫、1狗、2其他、品种breed、体重weight、年龄age、性别gender、是否绝育neutered、疫苗状态vaccinated、宠物照片photo、习性备注habits。这块设计的坑在于“习性备注”用户可能写很长的文字也可能只写“胆小怕生希望上门时轻手轻脚”。字段类型用VARCHAR(500)就够如果允许用户传养宠注意事项文档之类的再考虑升级成TEXT。另外宠物照片建议只存一个访问路径不存二进制文件本身扔到服务器某个磁盘目录或者对象存储里。3.3 订单表状态机与金额数据的关键约束订单表t_order是整个系统最核心的表我先把核心字段给你列出来订单编号order_no唯一字符串格式建议yyyyMMddHHmmss加随机数方便后续对账发布人owner_id和 接单人caretaker_id分别建立索引关联宠物pet_id服务类型service_type0上门喂养、1遛狗、2寄养服务开始时间start_time、结束时间end_time服务地址address和经纬度lat、lng经纬度用来做同城筛选和距离排序订单金额amount必须用DECIMAL(10,2)类型千万别用FLOAT或DOUBLE浮点数的精度问题在金钱上能让你输得裤衩都不剩订单状态status0待接单、1已接单待服务、2服务中、3待确认完成、4待评价、5已完成、6已取消取消方cancel_role和 取消原因cancel_reason数据留痕后续纠纷仲裁有用创建时间create_time、更新时间update_time订单状态的流转我前面说过了对应到这张表上就是status字段的变更。我在代码里强烈建议不要直接在 Service 里散着修改 status而是把状态流转封装成独立的方法集中管理状态变更的前置判断和后续动作这样整个系统的状态跳转是可控的、可追踪的。3.4 评价表与收藏、浏览记录的辅助作用评价表t_comment字段主要有关联订单order_id唯一保证一单一评、评价人user_id、被评人target_user_id、星级rate1到5、内容content、创建时间。这里要特别给order_id加唯一约束防止重复评价。收藏表t_favorite和浏览记录表t_view_log属于辅助表前者记录宠物主人收藏了哪个照看人或者照看人收藏了哪个需求后者记录用户浏览过哪些页面。这两张表在系统初期可能看不出多大价值但当你后期想做“推荐”功能时这些埋点数据就是最宝贵的原料。表设计这一节我最后补一句主键一律用自增Long类型不要用UUID字符串当主键页大小有限的情况下自增主键索引效率最高。唯一业务编号比如订单号用单独的业务字段存不要动主键。4. 后端核心链路从登录鉴权到订单状态流转的落地写法表结构设计好之后就到了撸代码的环节。这一章我挑四个核心模块展开讲讲分别是认证鉴权、需求发布与列表查询、订单状态流转、以及同城匹配的SQL写法。4.1 JWT登录鉴权的完整实现套路登录接口的逻辑很直接接收前端传来的手机号和密码先根据手机号查用户表查不到直接返回“用户不存在”查到了用BCrypt校验密码不匹配返回“密码错误”校验通过之后构建一个token里面包含用户id、手机号、角色这些关键信息。PostMapping(/login) public Result login(RequestBody LoginDTO dto) { // 1. 校验参数 String mobile dto.getMobile(); String password dto.getPassword(); // 2. 查询用户 User user userService.getOne( new LambdaQueryWrapperUser() .eq(User::getMobile, mobile) ); if (user null) { return Result.error(用户不存在); } // 3. 校验密码 (BCrypt加密) if (!BCrypt.checkpw(password, user.getPassword())) { return Result.error(密码错误); } // 4. 检查封禁状态 if (user.getStatus() 1) { return Result.error(该账号已被封禁请联系管理员); } // 5. 生成JWT String token JwtUtil.createToken(user.getId(), user.getMobile(), user.getRole()); return Result.success(new LoginVO(token, user)); }JWT的核心在JwtUtil这个工具类创建token的时候我习惯把过期时间设置为7天方便用户不用频繁登录解析token的时候抛异常就统一返回“登录已过期请重新登录”。为了省去每次业务代码里手写“从token取用户id”的重复劳动我写了一个LoginUserInterceptor实现了HandlerInterceptor在preHandle里解析token并把用户信息塞到ThreadLocal的静态工具类里。public class LoginUserInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (StringUtils.isBlank(token) || !token.startsWith(Bearer )) { throw new BusinessException(401, 未登录或登录已过期); } Claims claims JwtUtil.parseToken(token.replace(Bearer , )); if (claims null) { throw new BusinessException(401, 登录状态无效请重新登录); } LoginUserContext.set(new LoginUser(claims.get(userId, Long.class), claims.get(mobile, String.class), claims.get(role, Integer.class))); return true; } }这样做的好处是所有需要登录态的接口直接在 Service 里调LoginUserContext.get().getUserId()就能拿到当前用户不用来回传user_id参数代码干净很多。4.2 发布需求接口与订单初始化的边界处理宠物主人发布需求的接口需要从前端接收一组数据宠物id、服务类型、开始时间、结束时间、服务地址、经纬度、备注信息。这里我踩过一个坑服务时间的合法性校验非常重要如果用户选了今天早于当前时间的时间段后续整个订单的流转都会出问题。PostMapping(/order/create) public Result create(RequestBody OrderCreateDTO dto) { // 校验时间合法 (结束时间必须晚于开始时间) if (dto.getEndTime().isBefore(dto.getStartTime())) { return Result.error(服务结束时间不能早于开始时间); } if (dto.getStartTime().isBefore(LocalDateTime.now())) { return Result.error(服务开始时间不能早于当前时间); } // 校验宠物归属 Pet pet petService.getById(dto.getPetId()); if (pet null || !pet.getUserId().equals(LoginUserContext.get().getUserId())) { return Result.error(宠物信息不存在); } // 创建订单状态置为待接单(0) Order order new Order(); order.setOrderNo(OrderNoGenerator.generate()); order.setOwnerId(LoginUserContext.get().getUserId()); order.setPetId(dto.getPetId()); order.setServiceType(dto.getServiceType()); order.setStartTime(dto.getStartTime()); order.setEndTime(dto.getEndTime()); order.setAddress(dto.getAddress()); order.setLat(dto.getLat()); order.setLng(dto.getLng()); order.setRemark(dto.getRemark()); order.setStatus(0); orderService.save(order); return Result.success(发布成功, order.getId()); }这里有一个需要提醒的点订单初始状态一定得是“待接单”不要搞一个“草稿”状态出来增加复杂度除非你有存草稿箱的真实需求。4.3 同城需求列表经纬度距离排序的SQL写法同城宠物照看核心能力之一就是“按距离排序找附近的需求”。前端页面会传当前用户的经纬度过来后端要返回按距离从近到远排列的需求列表。我最开始图省事把所有数据捞出来在Java内存里算距离再排序结果数据量一上去CPU直接报警后来改成了MySQL内置函数处理。MySQL 5.7及以上版本内置了ST_Distance_Sphere函数可以直接算球面距离配合HAVING子句筛选半径范围一条SQL就把列表页搞定了。SELECT o.*, u.nickname AS owner_nickname, u.avatar AS owner_avatar, ST_Distance_Sphere( POINT(#{userLng}, #{userLat}), POINT(o.lng, o.lat) ) / 1000 AS distance_km FROM t_order o LEFT JOIN t_user u ON o.owner_id u.id WHERE o.status 0 HAVING distance_km #{radiusKm} ORDER BY distance_km ASC LIMIT #{pageSize} OFFSET #{offset}Java端只需要在 MyBatis 的 Mapper 接口里写对应注解SQL或者XML SQL把userLng、userLat、radiusKm、pageSize、offset传进去即可。实测数据量在万级以下时这个方案响应毫无压力等数据量到了百万级再考虑Geohash方案或者引入搜索引擎那是后话。距离单位这里要注意ST_Distance_Sphere返回的是米除以1000才是公里别换算错了闹出“明明在同一个小区却排到了5公里外”的笑话。4.4 接单、完成、评价的状态流转如何避免混乱订单状态流转是整个后端最容易写乱的地方。我的做法是单独建一个OrderStateMachine类把状态变更的合法跳转路径集中管理。public void changeStatus(Order order, int targetStatus) { int current order.getStatus(); switch (current) { case 0: // 待接单 if (targetStatus 1 || targetStatus 6) break; throw new BusinessException(当前状态不能变更为目标状态); case 1: // 已接单 if (targetStatus 2 || targetStatus 6) break; throw new BusinessException(当前状态不能变更为目标状态); case 2: // 服务中 if (targetStatus 3) break; throw new BusinessException(当前状态不能变更为目标状态); case 3: // 待确认完成 if (targetStatus 4 || targetStatus 2) break; throw new BusinessException(当前状态不能变更为目标状态); case 4: // 待评价 if (targetStatus 5) break; throw new BusinessException(当前状态不能变更为目标状态); default: throw new BusinessException(订单已结束无法变更状态); } order.setStatus(targetStatus); }这里有两个常见的业务动作要特别说明第一个是取消订单。待接单状态下宠物主人可以直接取消已接单状态下要区分是宠物主人取消还是照看人取消取消的时候需要说明原因而且要有一个限制比如照看人一周内主动取消次数超过3次冻结接单资格3天。这块逻辑用定时任务或者事件监听去做不要在取消接口里硬触发否则用户换个入口就能绕过限制。第二个是自动确认完成。照看人点击“申请完成”后订单状态变为“待确认完成”但宠物主人可能会忘记确认甚至故意不确认。解决方案是写一个Spring Boot自带的Scheduled定时任务每5分钟扫一次订单表把所有进入“待确认完成”状态超过24小时且未被任何一方申请仲裁的订单自动推进到“待评价”状态。Scheduled(cron 0 */5 * * * ?) public void autoConfirmCompletedOrders() { LocalDateTime deadline LocalDateTime.now().minusHours(24); ListOrder orders orderMapper.selectList( new LambdaQueryWrapperOrder() .eq(Order::getStatus, 3) .lt(Order::getUpdateTime, deadline) ); for (Order order : orders) { changeStatus(order, 4); orderService.updateById(order); } }这个定时任务别看代码短在真实项目里帮了大忙否则你会不停收到“对方一直不确认完成”的投诉工单。5. 前端页面与API对接Vue侧的关键实现细节后端接口写利索了前端这边工作量其实也不小。好消息是Vue Element UI这套组合做管理类和交易类界面非常顺手布局用el-container表单用el-form列表用el-table弹窗用el-dialog基本能满足绝大多数需求。5.1 Axios统一封装与登录态管理我习惯在src/utils/request.js里封装一份 axios 实例核心就是做三件事统一请求头、统一错误处理、统一token过期跳转。import axios from axios import { Message } from element-ui import router from /router const service axios.create({ baseURL: process.env.VUE_APP_BASE_URL || /api, timeout: 15000 }) // 请求拦截器自动携带token service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }) // 响应拦截器统一处理业务错误码和HTTP错误 service.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.msg || 请求异常) return Promise.reject(new Error(res.msg)) } return res }, error { if (error.response error.response.status 401) { Message.error(登录已过期请重新登录) localStorage.removeItem(token) router.push(/login) } else { Message.error(error.message || 网络异常) } return Promise.reject(error) } ) export default service路由守卫配合在src/router/index.js里beforeEach钩子统一判断router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next(/login) } else if (to.path /login token) { next(/) } else { next() } })这套做法属于麻雀虽小五脏俱全登录态管理、权限控制、错误提示都有了后续接权限路由也不费劲。5.2 首页需求列表与详情页的对接策略首页需求列表我用的el-card加栅格布局每个卡片展示宠物头像、昵称、服务类型、时间段、距离、预算。调接口时一个容易忽略的点是分页参数要同时传pageNum和pageSize后端返回的数据结构要统一成{ list, total }格式这样前端el-pagination直接能对上。详情页主要展示三块内容订单信息、宠物信息、宠物主人基础信息如果是照看人视角。这里我建议拆成三个组件分别调三个接口而不是让后端一次性返回一个大综合对象。原因很实际订单详情页在不同状态下展示的数据差异巨大比如待接单状态不能显示接单人的信息因为还没有人接单已完成状态又要显示评价入口大而全的接口反而把前端卡死了。5.3 发布需求页面的表单校验与交互细节发布页面是一张很长的表单包括选择宠物、服务类型、时间范围、地址、备注。Element UI 的el-form自带rules校验规则但有几个坑得提前填第一时间范围选择器el-date-picker的typedatetimerange返回值是一个包含两个时间字符串的数组提交前必须转换成后端约定的LocalDateTime格式用dayjs库格式化最省事。第二地址选择要调用高德地图或腾讯地图的JS SDK通过搜索选点方式拿到地址的经纬度。表单里不需要用户手动填经纬度只需要填结构化的地址文本经纬度从地图SDK的回调里取到后hidden存储。第三发布成功后不要直接router.push跳首页建议跳到订单详情页让用户看到自己刚发布的需求已经进入“待接单”状态交互上更有闭环感。5.4 个人中心与评价模块的实现要点个人中心页面包含三块信息我的信息、我的宠物、我的订单分角色看。我的订单列表我用了el-tabs分页签全部、待接单、进行中、待评价、已完成每个页签对应一个查询条件。评价模块是很容易被新人忽略的一块。评价接口提交时前端要同时传order_id、rate、content、target_user_id后端在保存评价时要校验“只有订单进行到待评价状态且当前用户是订单参与方才允许评价”这两层校验一个都不能少。评价完之后后端顺手更新被评价用户的好评率和接单次数这些统计数据会出现在照看人的个人主页上。6. 本地启动到部署上线的踩坑记录这个章节我打算写些掏心窝子的东西。这类项目真正的难点往往不在写业务代码而在从“代码写完了”到“系统跑起来了”这个过程每一步都有暗坑等着你。6.1 环境准备阶段最容易翻车的问题本地环境这套全栈项目需要的软件包括JDK 8或11、Maven 3.6、MySQL 5.7、Redis可选、Node.js 14。我见过太多新手卡在JDK版本上用JDK 17跑Spring Boot 2.7倒是能跑但有些老版本依赖会报模块化相关的错误直接劝退用JDK 8跑Spring Boot 3.x那是想都不要想框架直接不支持。数据库初始化时一定要看好SQL脚本里的字符集设置建库语句建议写成CREATE DATABASE pet_care DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;务必用 utf8mb4 而不是 utf8否则用户反馈里写个emoji表情存入数据库直接变乱码或者报错别问我是怎么知道的。Redis如果不想在本地安装项目里可以做一个降级方案写一个CacheService接口Redis实现和本地Map实现各来一个通过配置文件切换。这样用户没装Redis也能把系统跑起来不至于卡在基础设施上。6.2 跨域问题的本质与Nginx转发方案前端工程启动在localhost:8080后端接口跑在localhost:9090浏览器直接发ajax请求必然报跨域错误。开发环境下最简单的方式是vue.config.js里配置devServer代理module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:9090, changeOrigin: true } } } }这样前端请求/api/order/listdevServer会自动代理转发到http://localhost:9090/api/order/list后端不需要做任何跨域放行配置浏览器也感知不到跨域的存在。但不少教程喜欢在后端写个CorsConfig或者CrossOrigin注解来放行这里我多说一句开发环境用代理是正道后端不要设置allowCredentials(true)加allowedOrigins(*)这种组合。一旦前后端分离部署上线Nginx反向代理场景下这种配置会带来不必要的安全性困扰。我踩过这个坑线上排查了半天才发现是后端跨域配置太宽松导致的。生产环境部署方案如下前端npm run build生成dist目录扔到 Nginx 的 html 目录后端 jar 包放/opt/app目录用nohup java -jar pet-care.jar app.log 21 启动Nginx配置一个/api/的 location 转发到http://127.0.0.1:9090。server { listen 80; server_name your-domain.com; location / { root /opt/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:9090; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这个配置有两个注意点一是try_files $uri $uri/ /index.html必须加否则刷新非首页路由会404二是proxy_pass后面如果加了路径要注意和实际后端接口路径的匹配最省心的方式是proxy_pass http://127.0.0.1:9090;不加末尾斜杠原样转发。6.3 联调阶段真实遇到的字段不一致事故说个我真实经历过的联调事故。前端同事在《我的订单》页面调后端接口接口返回的订单状态字段值一直对不上前端显示的“待接单”实际后端状态是“已完成”。排查了快俩小时最后发现是后端实体类里的status字段被TableField(status)注解映射到了数据库的status列没问题但前端状态枚举定义如下// 前端枚举 const ORDER_STATUS { 0: 待接单, 1: 已接单, 2: 服务中, 3: 待确认完成, 4: 待评价, 5: 已完成, 6: 已取消 }而后端枚举类里把状态顺序定义成了0已取消、1待接单、2已接单...两边枚举对不上自然全乱了。这类问题在前后端分离的项目里极其常见根本原因是前后端共用一份状态契约但没有一个统一的文档来约束。建议项目早期就引入Swagger注解或接口文档平台每个枚举都写清楚含义联调前期能把80%的字段对齐问题扼杀掉。6.4 性能与安全性方面的几个小建议最后再聊几个实用的优化点适合你的项目上线前过一遍关于查询性能订单表的owner_id、caretaker_id、status三个字段务必建索引。这三个字段是列表页和状态查询的核心过滤条件不建索引等你订单量破万后会明显感受差距。关于接口安全发布需求和接单接口需要防重提交最简单的做法是前端按钮点击后加 Loading 禁用态后端用 Redis 的SETNX做幂等校验。虽然这个项目不至于被人薅羊毛但该有的习惯得有。关于文件上传宠物头像和照片上传时建议对文件类型做白名单校验只允许jpg、png、jpeg、webp同时限制单个文件大小不超过5MB。另外存储路径带上日期分目录比如/upload/2025/01/15/xxx.jpg不然一年下来文件全堆在同一个目录里找起来想死。关于日志后端启动后建议把logging.level.com.exampledebug打开一阵子能看清楚每条SQL和参数值排查问题效率翻倍。上线前再调回info级别避免日志刷屏。说实话这类前后端分离的实战项目只要把业务模型捋清楚、表结构设计好、状态流转控制住剩下的都是体力活。真正决定项目质量上限的是你在调试、部署和联调过程中积累的经验。我这篇基本把从零到上线整个链路上最容易踩的坑都替你先踩了一遍你照着做能省下大量抓耳挠腮的时间。最后再提醒一句上线之前一定把数据库备份做好这句话值一顿大餐。