ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

苍穹外卖项目开发到上线:环境配置、JWT认证、微信支付等坑位全解析

2026/9/9 8:27:23 拓冰建站 浏览量
苍穹外卖项目开发到上线:环境配置、JWT认证、微信支付等坑位全解析 1. 项目起手阶段最容易被卡住的环境问题很多人拿到苍穹外卖这套代码第一反应就是直接导入IDEA等着它自动下载依赖然后点启动。结果就是各种报错扑面而来其中有很多坑其实跟你写的业务代码没有半毛钱关系纯粹是环境层面的问题。1.1 Maven依赖下载不完整启动时冒出奇怪异常苍穹外卖用到的依赖不少Spring Boot、MyBatis、Druid、Lombok、Aliyun OSS SDK、微信支付SDK、WebSocket、Spring Cache等等全部加起来依赖树能拉得很长。前面几个常见的坑基本都是因为本地Maven仓库里缺失了某个Jar包或者是某个模块下载了一半就中断了IDEA编译的时候就会给你报出一堆程序包不存在或者找不到符号。我自己第一次跑这个项目的时候在启动阶段遇到的报错是java.lang.NoSuchMethodError: com.github.pagehelper.PageHelper.startPage查了半天发现是PageHelper的版本和我本地的MyBatis版本不兼容。苍穹外卖用的Spring Boot版本是2.7.x对应的MyBatis Starter版本和PageHelper版本是有配套关系的如果你自己改了pom.xml里的版本号或者IDEA里的Maven配置指向了错误的settings.xml导致某些依赖没有按预期解析就会出现这种类能引用到但方法不存在的情况。排查思路如下先确认Maven仓库路径里是否有snowy相关依赖没有就重新reimport。检查pom.xml中是否混入了你从其他项目复制的依赖版本号覆盖了项目原本的版本。在IDEA右侧Maven面板点击刷新让Maven重新解析所有依赖。如果本地的.m2仓库里的某些Jar包出现.lastUpdated后缀文件说明之前下载失败了需要手动删掉这些残留文件再重新下载。我建议是不要随意升级苍穹外卖自带的核心依赖版本Spring Boot 2.7.x对应对的MyBatis Starter和PageHelper版本项目里都是调试好的你手贱升级了很可能就会踩到与缓存、分页、插件相关的兼容性坑。1.2 数据库初始化脚本执行到一半失败苍穹外卖配套的SQL脚本分了好几个文件有的文件里建表、插入数据混在一起。新手拿到脚本直接一股脑往Navicat里执行经常会报Table already exists或者Duplicate column name之类的错误。这里最让人头疼的是脚本执行到一半失败数据库里已经产生了部分表这时候你重新执行脚本就会因为表已经存在而报错。解决办法只有一个先手动把所有相关表全部Drop掉再重新执行脚本。另外一个细节是MySQL版本差异。这套项目默认的SQL语法对MySQL 5.7和MySQL 8.0都兼容但如果你用的是8.0版本并且数据库连接串里没有设置useSSLfalse和serverTimezoneAsia/Shanghai启动的时候会一直报时区相关的警告甚至导致Druid连接池初始化失败。在application.yml里数据源配置建议这样写spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/sky_take_out?serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf-8useSSLfalseallowPublicKeyRetrievaltrue username: root password: your_passwordallowPublicKeyRetrieval这个参数很多人会忽略但MySQL 8.0的驱动在默认情况下如果使用的是caching_sha2_password认证方式且没有配置公钥检索连接时会直接报Public Key Retrieval is not allowed。这个小坑能卡住你差不多半小时。1.3 Redis未启动导致服务起不来苍穹外卖大量使用了Redis做缓存尤其是菜品、分类、购物车这些模块。项目启动本身不会强制检测Redis但你一调用接口几乎就会碰到缓存读写如果Redis没有启动服务虽然不会挂但接口会一直报连接异常。我见过不少人在本地开发时启动了后端服务、启动了前端Nginx却忘了启动Redis结果登录接口都登不了。因为登录后要往Redis里存JWT TokenRedis连不上Token存不进去登录自然就失败了。建议把Redis设置成开机自启或者至少每次开发前先检查一下Redis进程是否存在。Windows下可以用redis-server.exe直接启动注意启动后保留命令行窗口不要关闭。2. JWT登录认证链路里的连环坑登录认证是苍穹外卖整个项目里最核心的链路之一也是最容易出问题的地方。从JWT的生成、校验到拦截器配置再到用户信息如何从拦截器传递到Controller层每一步都有坑。2.1 Token过期时间与登录状态的矛盾苍穹外卖的JWT工具类里默认的过期时间可能只有几十分钟如果你在熬夜调接口的时候发现过一会儿就跳回登录页不要急着怀疑代码逻辑先看一下Token过期时间设置。JWT工具类中通常有一段类似这样的代码// 设置过期时间 JWTBuilder builder Jwts.builder() .setClaims(claims) .setIssuedAt(now) .setExpiration(new Date(now.getTime() 7200000)); // 2小时但这个过期时间如果设置得太短比如项目默认的是30分钟那么你在开发阶段经常会出现明明刚登录成功下一个接口就报401的情况。实际开发中如果不需要特别严格的安全控制建议把这个过期时间至少设置成2小时到一天方便联调。但是注意改过期时间只是临时解决联调问题真正上线前还是要根据业务需求把时间调回合理范围并且要配合Token续期机制否则用户用着用着就被踢下线了。2.2 拦截器配置顺序导致登录接口本身被拦截这个坑非常经典。苍穹外卖中管理端和用户端各有一套拦截器分别拦截/admin/**和/user/**路径。新手容易犯的错误是在配置拦截器时把所有接口都拦截了结果登录接口自己也进不来导致登录请求直接被拦截器拦截下来然后拦截器尝试解析Token发现没有Token直接返回未登录错误。排查方法很简单先看控制台是否有拦截器相关的日志输出。在拦截器的preHandle方法里打日志看请求是否进入了拦截器。检查WebMvcConfigurer中addInterceptors方法里的excludePathPatterns是否包含了登录接口。正常配置应该是这样的思路registry.addInterceptor(jwtTokenAdminInterceptor) .addPathPatterns(/admin/**) .excludePathPatterns(/admin/employee/login);还有一个细节是如果管理端和用户端都实现了天气、店铺、菜品之类的公共接口而这些接口不需要登录就能访问那么同样需要在对应的拦截器配置里排除掉否则前端页面在未登录状态下直接访问这些接口也会报错。2.3 ThreadLocal里取不到用户信息空指针一片苍穹外卖为了在Controller层直接获取当前登录用户的ID会通过拦截器在解析Token成功后把用户ID存到ThreadLocal里。逻辑是这样的JWT拦截器解析出用户ID然后BaseContext.setCurrentId(userId)Controller层通过BaseContext.getCurrentId()获取。这个设计本身没什么问题但有一个隐患如果某个请求没有经过拦截器但Controller里却调用了BaseContext.getCurrentId()就会拿到null然后在操作数据库时直接空指针。举一个具体的场景在用户端下单流程中Controller的方法会从BaseContext中获取当前用户ID然后插入订单表。如果你把下单接口放在了不需要JWT认证的路径下或者前端在调用下单接口时没有携带Token那么拦截器根本就不会执行BaseContext里面就是空的。另外还有一个坑ThreadLocal的内存泄漏问题。Tomcat的工作线程是复用的一个线程处理完请求A之后线程池会把这线程归还如果请求A在ThreadLocal里存了用户ID却没有在请求结束时清除下一个请求B复用了这根线程那么请求B在Controller里拿BaseContext.getCurrentId()拿到的就是请求A的用户ID。这会造成严重的数据串号问题。正确的做法是在拦截器的afterCompletion方法里执行清除操作Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { BaseContext.removeCurrentId(); }如果不加这行代码开发阶段很难发现因为大多数情况下请求都会经过完整的认证流程ThreadLocal每次都会被重新覆盖但一旦某个请求因为异常没有走到后续逻辑或者并发上来之后串号问题就会暴露出来。2.4 JWT密钥泄露与算法选择开发阶段很多人的JWT密钥直接写在配置类里硬编码在代码中。这本身不算坑但如果你把项目提交到GitHub公开仓库这个密钥就相当于暴露了任何人拿到密钥都能伪造一个合法的Token直接调用你的管理端接口。我建议把JWT的签名密钥放到配置文件中并且在生产环境通过环境变量注入sky: jwt: admin-secret-key: your_secret_key_here admin-ttl: 7200000 admin-token-name: token另外JWT的签名算法在项目里默认用的可能是HS256这是一种对称加密算法生成和验证都使用同一个密钥。如果你有多个微服务需要验证Token建议使用非对称算法如RS256但苍穹外卖是一个单体项目HS256足够了。3. 微信支付与订单状态流转最容易让人抓狂的模块只要涉及支付复杂度就瞬间上升一个档次。苍穹外卖里集成了微信支付的下单、回调、退款部分版本流程而且前端有支付模拟的功能。这里面的坑主要集中在回调验签、金额计算、状态更新顺序。3.1 微信支付回调验签失败的完整排查链路很多人在联调微信支付时都遇到过回调通知一直验签失败的问题。微信支付成功之后微信服务器会往你配置的通知地址发一条POST请求里面带着订单号、支付金额、签名等信息。你的后端需要对签名做校验确认这个回调真的是微信发来的而不是恶意伪造的。验签失败的常见原因有这么几个APIv3密钥配置错误苍穹外卖里涉及到APIv3密钥很多同学会把商户API密钥和APIv3密钥搞混。这两个是不同的东西APIv3密钥是你自己设置的32位字符串用于回调数据解密和证书的敏感信息解密。证书序列号不匹配微信支付要求使用商户证书配置时填写的证书序列号必须和apiclient_cert.p12或者apiclient_key.pem里的信息一致。序列号可以在微信商户平台的API安全里看到注意不要复制错误。回调数据解密失败微信支付回调的报文是加密的需要用APIv3密钥和证书私钥来解密。如果解密工具类写得有问题或者密钥配置错误就会报AEADBadTagException之类的异常。通知地址不可达如果你开发的时候回调地址填的是localhost微信服务器根本访问不到你的本机。解决办法是用内网穿透工具把本机的HTTP服务暴露到公网然后在商户平台把这个公网地址配置为回调地址。排查这类问题我建议按这个顺序来先确认商户平台的回调地址配置是否正确是否指向了你本地穿透后的公网地址。在回调接口入口处加日志打印接收到原始报文确认微信是否真的发了回调。单独写一个测试类用错误单号和正确单号分别测试验签和验签失败的逻辑。确认APIv3密钥、商户号、证书序列号这三者是否从同一个商户账号下获取的很多人会同时使用测试号和生产号导致配置串了。3.2 金额计算用Double导致的一分钱误差苍穹外卖在订单金额计算上如果直接把前端传过来的金额当成double来算然后存到数据库很容易出现精度问题。比如前端传的是88.8后端用double运算再序列化为88.80在比较大小或者做支付金额校验时可能就会出现因为浮点误差导致的不相等。这里正确的做法是全程用BigDecimal并且金额字段在数据库中用decimal(10,2)类型。前后端交互时金额统一用分整数来传输或者是统一用字符串传输避免浮点数换算。我自己在实际操作中发现苍穹外卖的订单金额计算这块很多逻辑会涉及多个菜品、多个购物车条目如果中间某一步用了double最后汇总金额时很容易出现细微偏差。微信支付对金额的要求是精确到分如果你的金额是88.80000000000001传给微信支付会直接报参数错误所以必须确保每一步金额运算都用BigDecimal并且最终setScale(2, RoundingMode.HALF_UP)。3.3 订单状态机流转的条件边界苍穹外卖的订单状态一般有待付款、待接单、已接单、派送中、已完成、已取消、退款中等等。状态流转必须严格按顺序走否则会出现逻辑漏洞。最容易踩的坑是超时未支付自动取消订单的任务和用户手动取消订单的操作并发执行。比如用户在前台点了取消订单同时Spring Task定时任务扫描到超时订单也触发了取消操作两个请求同时执行导致订单状态被更新了两次出现已取消状态的订单又被送到了商家端。解决思路在更新订单状态的SQL中加上状态条件比如UPDATE orders SET status 已取消 WHERE id ? AND status 待付款这样只有当订单处于待付款状态时才能取消成功。利用数据库行锁或者乐观锁在更新前先查询当前状态再在更新时用状态字段作为条件确保只有一条更新生效。苍穹外卖里定时任务取消订单的逻辑写在了OrderTask中用了LocalDateTime.now()和下单时间做对比。这里的坑在于如果定时任务每隔1分钟执行一次而订单的超时时间是15分钟那么订单实际取消的时间可能不是精确的15分钟而是15分钟到16分钟之间取决于定时任务的触发时机。这个问题在业务上可以接受但你需要知道它存在。4. Spring Cache缓存与数据库不一致的坑苍穹外卖大量使用了Spring Cache注解Cacheable、CachePut、CacheEvict来缓存菜品、分类等信息。用起来确实方便但如果不理解缓存失效的机制就会出现数据库改了前端看到的还是旧数据的诡异问题。4.1 缓存更新策略选错菜品改了前端不刷新比如管理端修改了一个菜品的信息按正常逻辑这个菜品在用户端的展示应该立即更新。但如果用户端查询菜品时走的是Cacheable缓存而管理端修改菜品时没有清缓存那用户端继续从缓存里读旧数据前端就看不到变化。苍穹外卖里管理端的菜品新增和修改操作通常会调用清理缓存的逻辑但这里容易出问题的地方是缓存key的命名规则必须和查询时的key保持一致。比如查询菜品时key可能是categoryId:1、status:1等组合参数而更新菜品时如果只清理了categoryId:1这个key但缓存实际存储时用的key是带分类ID和状态一起组合的那就清不掉。我个人的建议是在涉及缓存清理时先理清楚缓存key的生成规则。Spring Cache默认的key生成策略是参数值或key属性指定的值苍穹外卖里通常会在Cacheable注解上指定key的值比如Cacheable(cacheNames setmeal, key #categoryId)那么在清理时也应该用同样的key来清理CacheEvict(cacheNames setmeal, key #categoryId)如果key写错了缓存清理不彻底前端的旧数据就会一直存在这种问题非常难排查因为代码逻辑看起来完全正常。4.2 缓存穿透和缓存雪崩的初级处理在高并发场景下如果某个分类下没有任何菜品那么查询结果就是空的如果不对空结果做缓存那么每次请求都会打到数据库这就是缓存穿透。苍穹外卖里其实没有做太严格的防护因为教学项目嘛但你自己上线的话就要注意这一点。Spring Cache默认情况下对null值是不会缓存的也就是说如果数据库中查询结果为nullCacheable不会把null缓存下来。这就意味着如果某个热点菜品ID在数据库里不存在而恶意用户不断用这个ID发起请求每次都会穿透缓存打到数据库。解决缓存穿透的一个简单方案是缓存null值并设置较短的过期时间。但Spring Cache本身不支持缓存null你需要自定义CacheManager中的RedisCacheConfiguration设置allowNullValues为true。另外缓存雪崩的初级处理方法是给不同的缓存key设置不同的过期时间避免同一时间大量缓存同时失效导致数据库压力瞬间增大。Redis的key过期时间如果不设置随机范围在整点关闭缓存的时候数据库会被打崩。4.3 Redis缓存和MySQL事务的一致性苍穹外卖里有些操作是先操作数据库再清理缓存。如果数据库更新成功了但是清理缓存时Redis刚好网络抖动缓存清理失败那前端继续读到旧数据。严格意义上要保证缓存和数据库的一致性需要用到一些分布式事务组件或者消息队列但作为单体项目没有必要引入这么重的机制。在使用Spring Cache时可以尽量把CacheEvict放在事务提交之后执行比如用TransactionalEventListener(phase TransactionPhase.AFTER_COMMIT)监听事务提交事件在事务提交后再清缓存。不过这个优化对于苍穹外卖这种教学项目来说属于锦上添花不是必须的。实际开发中只要确保数据库操作和缓存清理的顺序是对的——先改数据库再清缓存——大多数场景下就够用了。5. WebSocket推送与Spring Task定时任务需求背后的隐藏细节苍穹外卖里有两个比较有意思的技术点WebSocket推送订单提醒以及Spring Task定时任务处理超时订单。这两个功能单独看都不算难但结合起来使用会有一些非常隐蔽的坑。5.1 WebSocket连接建立失败或掉线苍穹外卖里的WebSocket主要用于用户端下单后向管理端推送新订单提醒。管理端浏览器和服务器建立WebSocket连接服务器在收到新订单事件后通过WebSocket主动推送消息给管理端页面。WebSocket连不上的常见原因握手请求没有带上Token苍穹外卖在WebSocket的拦截器里做了JWT校验如果前端在建立WebSocket连接时没有把Token放在Sec-WebSocket-Protocol或者其他header里服务端握手时解析不到Token直接拒绝连接。开发时前端的WebSocket对象在构造时需要手动拼接Token参数很多人在这一层就卡住了。Nginx代理没有配置WebSocket协议如果你通过Nginx做反向代理访问前端页面Nginx默认配置中缺了Upgrade相关的Header设置WebSocket连接会被Nginx直接干掉。Nginx配置需要加上这一段location /ws { proxy_pass http://backend_server; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_read_timeout 3600s; }WebSocket连接空闲被断开如果WebSocket连接长时间没有消息往来中间的网络设备可能会自动断开空闲连接。服务端需要实现心跳机制定时向客户端发送ping消息保持连接活跃。排查WebSocket问题可以在前端浏览器的Network面板里查看WebSocket的握手请求状态码。如果是101说明握手成功如果是403或者401说明拦截器拦截了如果是404说明路径配错了如果是200说明Nginx把那根升级协议的头给吞了。5.2 Spring Task的Cron表达式与并发执行苍穹外卖的Spring Task定时任务最常见的坑就是Cron表达式写错。比如你想让任务每天凌晨2点执行却写成了0 0 2 * * ?这个是对的。但如果你写成了0 0 2 1 * ?那就会变成每个月1号凌晨2点才执行一次。还有一个更隐蔽的坑是Spring Task默认是单线程串行执行的。也就是说如果同时配置了多个定时任务并且某个任务执行时间比较长那么其他任务就会被阻塞排队导致后续任务的执行时间严重延迟。这个坑在实际业务里非常致命。苍穹外卖里有一个定时任务在扫描超时未支付订单如果这个任务执行时间过长比如数据库连接池满了或者SQL太慢那么后面定时清理购物车的任务、定时更新店铺营业状态的任务都会延后执行。调整方案是在配置类里设置TaskScheduler线程池大小Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler new ThreadPoolTaskScheduler(); scheduler.setPoolSize(10); scheduler.setThreadNamePrefix(sky-task-); return scheduler; }设置之后多个定时任务就可以并行执行了。但要注意如果多个任务同时操作同一张表需要考虑并发问题否则就会出现我前面提到的订单状态重复更新问题。5.3 WebSocket推送与订单状态不同步WebSocket推送订单提醒的触发点在订单创建方法里。这个推送操作做完之后如果推送成功但订单创建的事务之后回滚了比如插入订单明细失败那么管理端就会收到一个新订单提醒但数据库里根本没有这个订单。这个问题的本质是WebSocket推送的网络操作和数据库事务不在同一个事务边界内推送成功不能代表数据库提交成功。解决办法是把WebSocket推送放到事务提交之后执行也就是在Service方法返回之前先通过TransactionSynchronizationManager.registerSynchronization注册一个事务同步回调在afterCommit阶段再推送WebSocket消息。这样可以确保只有事务提交成功之后才会给管理端推送新订单提醒。不过这种优化对于开发阶段来说可能感觉不到差异因为本地数据库事务很少失败。但上线之后如果数据库压力大事务回滚的概率增加这个问题就会暴露出来。6. 数据库连接池耗尽和SQL性能隐患苍穹外卖作为一个教学项目在数据量小的时候SQL性能问题表现得不太明显但只要数据量上来了或者并发稍微高一点就会出现各种隐藏的坑。6.1 HikariCP连接池耗尽Sky外卖的数据源默认用的是HikariCPSpring Boot 2.x的默认连接池。HikariCP性能很好但默认配置只给了10个连接。如果某个接口比较慢或者出现了慢SQL10个连接很容易全部被占满后续的请求就会一直等待获取连接表现为接口卡死前端请求一直转圈。我遇到过一次很典型的场景管理端导出Excel报表这个操作本身需要执行多条SQL语句中间还有比较耗时的统计逻辑。如果多个管理员同时导出连接池瞬间被耗尽导致其他正常的接口比如登录、菜单查询也全部超时。解决办法排查慢SQL给频繁查询的字段加上索引。在application.yml中适当调大连接池大小比如spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000对于耗时操作比如导出、报表类功能不要在主线程同步执行而是放到异步队列里。苍穹外卖这类操作不多但你要有这个意识。6.2 MySQL索引失效的几个典型场景苍穹外卖里菜品搜索、订单列表、分类查询这些高频操作如果SQL没有合理使用索引性能就会很差。这里有三个索引失效的典型场景很多新手会踩到对索引列使用了函数比如WHERE DATE(create_time) 2024-01-01即使create_time字段有索引因为对列使用了DATE()函数索引就失效了。应该改成范围查询WHERE create_time 2024-01-01 00:00:00 AND create_time 2024-01-02 00:00:00。隐式类型转换比如表中phone字段是varchar类型但查询时传的是数字类型MySQL会自动做隐式转换导致索引失效。所以写SQL查询时字符串字段一定要用引号包裹。前模糊查询WHERE name LIKE %关键词%这个查询无法使用普通索引。如果业务上需要这种搜索功能建议改成全文索引或者使用专门的搜索引擎。针对订单表的查询苍穹外卖中管理端订单搜索的核心条件通常是时间范围订单号你可以给order_time和order_number分别建立索引搜索效率会提升很多。6.3 数据库连接断开后重连在开发阶段MySQL可能因为长时间没有操作而断开了连接再次访问时Druid或HikariCP如果配置不当不会自动重连就会报Connection is not available或者Communications link failure。HikariCP默认带有自动重连机制但需要配置一下spring: datasource: hikari: max-lifetime: 1800000 idle-timeout: 600000max-lifetime必须小于MySQL的wait_timeout否则连接会被MySQL服务端先断掉而连接池还不知道拿到已失效的连接自然就报错了。这个配置在生产环境尤其重要。7. 前端联调中容易被忽略的接口问题苍穹外卖的前端是一个Vue项目Nginx托管很多时候后端接口明明没问题但前端就是调不通。这些坑在前端联调阶段非常典型。7.1 Nginx反向代理导致请求404或跨域苍穹外卖的前端代码里接口地址通常配置的是一个相对路径比如/admin/**实际请求时通过Nginx反向代理转发到后端服务。如果你的Nginx配置里没有把/admin路径代理到后端地址而是直接去前端静态文件目录里找结果就是404。Nginx的关键配置大概长这样location /admin/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }配置好之后记得执行nginx -s reload重载配置。这个步骤容易漏掉改完配置不重载前端依旧报404。另外如果你不使用Nginx而是直接用Vite开发服务器需要配置proxy实现跨域转发否则浏览器会直接拦截跨域请求报CORS错误。7.2 前端页面一直加载不出图片或菜品列表为空如果前端页面能打开但用户的菜品列表一直为空有一个非常隐蔽的原因管理端没有给菜品设置分类ID或者菜品ID在数据库中被设置为0。前端查询菜品列表时通常会带上分类ID作为查询条件如果菜品的分类ID为空用户端在分类切换时就查不到数据。还有一个情况是菜品表中status字段为0表示停售如果所有菜品都没上架用户端也会显示空列表。这是业务上的坑不算技术问题但很容易让人误以为后端逻辑有Bug。7.3 订单提交成功但前端跳转失败订单提交成功后前端应该跳转到支付页面。如果后端返回了订单ID但前端没有正确解析或者支付二维码接口返回的数据格式不对页面就会停留在原地。这种问题我建议先打开浏览器的Network面板看接口返回的数据结构是否和后端接口文档一致。苍穹外卖的接口返回统一是Result对象里面有code、msg、data三个字段如果前端代码里取的是data.orderId而后端返回的字段名是orderNumber那肯定拿不到。此类问题没什么高深的调试技巧就是仔细对字段名。后端改了字段名前端没同步改是联调阶段最常见的坑。8. 部署上线时的Linux环境坑项目要真正上线就要部署到Linux服务器。这里面的坑也不少而且每个坑都可能让你加班到凌晨。8.1 端口被防火墙拦截部署到服务器后公网访问不到8080端口很多人的第一反应是服务没启动成功。其实大部分情况是云服务器的安全组没有放行8080端口或者服务器本机的firewalld防火墙拦截了端口。排查步骤在服务器本机执行curl http://localhost:8080看是否返回数据。如果本机可以访问但外网不能访问检查云服务商的安全组规则。如果是自建服务器检查firewalld或iptables规则firewall-cmd --zonepublic --add-port8080/tcp --permanent firewall-cmd --reload8.2 日志文件无限增长磁盘撑满如果项目直接使用nohup java -jar xxx.jar app.log 来启动日志文件会无限增长时间长了会把磁盘撑爆。建议使用logrotate或者logback自带的时间滚动策略在logback-spring.xml里配置按天切割日志并设置保留天数appender nameFILE classch.qos.logback.core.rolling.RollingFileAppender filelogs/sky.log/file rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePatternlogs/sky.%d{yyyy-MM-dd}.log/fileNamePattern maxHistory30/maxHistory /rollingPolicy /appender日志是排查线上问题的重要手段但如果日志本身不可控反而会成为灾难。8.3 配置文件里的开发环境地址没有替换部署到生产环境时最怕的就是配置文件里还留着开发环境的地址。比如数据库地址还是localhostRedis地址还是127.0.0.1而生产环境的数据源在另一台服务器上服务启动后一直连不上数据库。建议把配置文件中容易变化的部分数据库地址、Redis地址、文件存储路径、支付密钥全部提取到application-prod.yml里通过spring.profiles.activeprod来切换。这样一个项目可以同时保留dev、test、prod三套配置部署时只需要选择对应的profile就行。我在实际操作中养成了一个习惯每次部署前先检查bootstrap.yml或application.yml中是否有localhost、127.0.0.1这种地址只要有一个没改掉就不用往下看了。这个习惯帮我避免了很多线上事故。9. 多环境配置切换与部署顺序的建议写到这里我把苍穹外卖从开发到上线过程中最常踩的坑都梳理了一遍。最后分享一个我自己的部署顺序参考方案这个顺序可以帮你把坑尽量挡在早期。环境准备阶段先确认JDK版本、MySQL版本、Redis版本不要用太新或太旧的版本尽量跟项目要求一致。数据库迁移先执行SQL脚本确认核心表都能建好再启动后端服务。后端启动验证启动后端后先用接口测试工具Postman/Apifox调用几个核心接口登录、获取菜品分类确认接口返回正常。前端Nginx配置确认Nginx配置文件里的反向代理路径正确静态资源路径正确。支付联调如果涉及微信支付先用测试号联调确认回调地址能通再换成正式商户号。上线前压测用简单工具JMeter做一次并发测试确认连接池设置合理不会因为并发过高而挂掉。这个顺序不一定适用于所有情况但按照这个思路走大部分问题都能在早期暴露而不是等到部署到服务器上才手忙脚乱地排查。我写这篇文章之前正好又帮一个同事排查了一个WebSocket连接不上的问题最后发现是Nginx配置里少了Upgrade Header。这类问题本身并不复杂但对于没接触过的人而言确实够折腾一阵子。希望这篇文章能帮你把苍穹外卖里的这些坑提前填平。