先交代一个背景我去年给本地一家家政公司做过一套同城上门服务App的全套部署从源码编译到双端上架再到服务器运维都走了一遍。整个过程踩的坑不少但部署路径其实是可以标准化复用的。这篇我直接按“方案详解”来写面向需要自己搞定源码部署、又不想被繁琐步骤劝退的开发者。假如你手头刚拿到一套同城上门服务的App源码不管是Java后端还是Node后端移动端不管是用uniapp还是Flutter做的核心部署链路都是相通的。我会用一套最典型的组合来讲Spring Boot uniapp MySQL Redis MinIO Nginx这套方案在小团队和外包项目里出镜率最高容易落地也方便你后续改造成自己的业务。很多人拿到源码第一步就懵因为源码里混杂了后端API、管理后台、用户端App、师傅端App还有各种配置文件。其实你需要做的不是“看懂每个文件”而是先把整套系统的运行链路理清楚再按“数据库-后端-对象存储-前端打包-线上运维”的顺序去部署。只要链路通了剩下的都是配置细节。1. 同城上门服务App的技术盘点与设计思路1.1 这类App到底由几个端组成同城上门服务典型业务是家政保洁、维修、上门做饭、宠物照护、代取快递这类场景。表面上你只需要一个用户App实际上完整的系统至少包含四个部分用户端App线上选服务、下单、支付、查看订单进度、评价。服务师傅端App接单、上门打卡、上传完成凭证、提现。管理后台管理服务分类、定价、师傅审核、订单仲裁、数据统计。后端API服务连接App和数据库处理业务逻辑、消息推送、支付回调。这四个部分不是互相独立的。用户端下单后后端要生成订单并推送给师傅端师傅端操作后用户端要收到状态变化。所以你在部署时必须保证几个端使用同一套API接口和数据源而不是各自连一套数据库。我在部署时发现很多人会犯一个错只把用户端App装到手机上看效果结果师傅端和管理后台不联调等真上线才发现订单状态不同步。所以方案里第一个要坚持的原则是“先打通全链路再包装App”。实际项目里前端App可能拆成两个App用户端、师傅端也可能是一个App里做角色切换。源码结构上差异很大但部署思路一样每个端都要通过HTTPS请求后端API携带Token后端按角色控制权限。1.2 技术栈选型我为什么推荐这套组合先说结论再讲理由。我用的是后端Java Spring Boot或者轻量一点的Go、Node.js考虑到源码生态Spring Boot最常见。移动端uniapp一码多编能同时输出Android、iOS和小程序。管理后台Vue Element UI。数据库MySQL 8.0存储订单、用户、师傅、交易流水等结构化数据。缓存Redis做验证码、Token、订单超时、热点分类缓存。对象存储MinIO存头像、服务实拍照片、资质证件。反向代理Nginx统一入口、HTTPS证书、静态资源托管。部署方式Docker Compose环境一致性最好一台2核4G的云服务器也能跑起来。为什么不用“一顿操作猛如虎”的微服务因为同城上门服务的业务量在早期通常没大到需要拆服务。一个单体后端扛着配合Redis和MySQL连接池优化日常几千单根本不会成为性能瓶颈。拆成微服务反而会让源码部署变成噩梦光服务发现、配置中心、网关就够你折腾好几天的。移动端选uniapp也是同样的逻辑团队里不一定要同时养一个iOS原生和一个Android原生开发。uniapp打包出来的App在性能和体验上足够满足上门服务场景地图、定位、支付、推送都支持原生插件调用。如果你拿到的源码本身就是原生安卓或者Flutter那也没关系部署的服务器端部分完全一样只是打包方式不同。这套组合的另一个好处是资料多踩坑容易被检索到。哪怕你从零开始也能在网上找到大量针对“Spring Boot uniapp Docker”的教程。对于部署这种对稳定性要求高、又需要长期维护的事情生态完整比单纯追求技术新更重要。2. 部署环境准备与源码工程解析2.1 服务器选型与基础环境初始化同城上门服务App如果只服务一个城市前期流量压力不大。我建议服务器从2核4G起步带宽5Mbps左右。如果你的服务会涉及图片大量上传比如师傅上门前后拍照片、用户传房间实拍图那带宽可以提到10Mbps不然高峰期图片加载会明显变慢。操作系统选Ubuntu 20.04或者22.04 LTS原因是命令文档多、Docker支持好。如果你习惯CentOS也可以但注意防火墙命令和软件源略有差异。初始化第一步是创建普通用户禁止root直接登录把SSH端口改掉并配置密钥登录。这一步别省尤其是你要用公网IP登录服务器的话端口扫描机器人比你想象中多得多。接着安装Docker和Docker Compose。我习惯用阿里云或者腾讯云的软件源安装命令比较直接sudo apt update sudo apt install -y docker.io docker-compose-v2 sudo systemctl enable docker --now装完后检查一下版本docker --version docker compose versionDocker Compose v2现在直接作为docker的子命令使用也就是docker compose注意不是以前的docker-compose。很多老教程还让你单独装docker-compose新版环境其实不需要。2.2 拿到源码后先做三件事这一步非常关键直接决定你后面能不能顺利跑起来。无论源码来自Gitee、GitHub还是商业渠道先别急着启动按顺序做三件事列出完整目录结构找到后端、移动端、管理后台各自的位置。找文档目录下的SQL文件和配置文件模板比如application-prod.yml、.env.example、config.php这类。检查核心依赖版本比如JDK版本、Node版本、MySQL版本避免本机和服务器版本差异导致的兼容问题。我自己做过的项目中源码通常长这样project/ ├── server/ # 后端Java项目 │ ├── src/ │ ├── pom.xml │ └── application.yml ├── admin/ # 管理后台Vue项目 │ ├── src/ │ ├── package.json │ └── .env.production ├── app/ # uniapp移动端 │ ├── pages/ │ ├── manifest.json │ └── config.js ├── doc/ │ └── sql/ │ └── init.sql └── docker-compose.yml如果不是这个结构也没有关系但你需要自己摸清每部分的入口。拿到源码就盲目执行npm run dev是最容易踩坑的因为服务端没起、数据库没建前端拿不到接口自然觉得源码有问题。2.3 从本地编译到可运行本地编译不是为了本地跑起来而是提前发现代码层面的问题。后端如果用Maven那么先确认JDK版本。Spring Boot 2.x通常需要JDK 8或11Spring Boot 3.x需要JDK 17。cd server mvn clean package -DskipTests如果Maven下载依赖很慢建议配置阿里云镜像。这一步成功后会生成target目录下的jar包。接着处理管理后台cd admin npm install npm run buildnpm run build以后会生成dist目录这就是后面部署到Nginx的静态文件。uniapp端因为是构建成App不是普通网站所以本地编译更多是预览调试后面打包成apk或者ipa再安装到手机。我在实战里习惯先把后端和管理后台跑通再碰App。因为App遇到的网络问题会比后端多如果后端还没确认可用App一报错你会分不清是接口问题还是客户端问题。3. 后端服务与数据存储部署3.1 MySQL初始化与主从准备数据库是整个系统最核心的资产部署的时候我建议单独跑一个MySQL容器而不是和后端挤在一起。用Docker Compose编排时需要指定数据目录挂载这样即使容器崩溃数据也还在。下面是一段我常用的MySQL服务配置你可以根据自己的源码需求调整端口和密码services: mysql: image: mysql:8.0 container_name: app-mysql restart: always environment: MYSQL_ROOT_PASSWORD: RootPassw0rd MYSQL_DATABASE: door_service MYSQL_USER: doorservice MYSQL_PASSWORD: ServicePassw0rd ports: - 3306:3306 volumes: - ./data/mysql:/var/lib/mysql - ./doc/sql/init.sql:/docker-entrypoint-initdb.d/init.sql:ro command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci这里有两个重点。第一字符集一定要用utf8mb4因为用户昵称、备注里可能出现表情符号如果用默认的utf8一旦写入生僻字或emoji就会报编码错误。第二初始化SQL脚本放在/docker-entrypoint-initdb.d/目录下MySQL容器首次启动时会自动执行省得你手动导入。如果你拿到的源码没有提供SQL文件那你就需要根据实体类动态建表或者用数据库迁移工具。但大多数商业源码都会附带完整的init.sql。启动后验证一下数据库docker compose up -d mysql docker exec -it app-mysql mysql -u root -p进去之后执行show databases;如果看到door_service库并且表已经生成说明初始化成功。3.2 Redis缓存与热数据处理Redis在系统里主要承担三个功能用户Token缓存、短信验证码、订单超时处理。部署相比MySQL简单很多但要注意安全和持久化。services: redis: image: redis:7.0-alpine container_name: app-redis restart: always command: redis-server --requirepass RedisPassw0rd --appendonly yes --port 6379 ports: - 6379:6379 volumes: - ./data/redis:/data--appendonly yes是开启AOF持久化保证Redis重启后不丢数据。生产环境必须设置requirepass否则Redis裸奔在公网上等于把缓存数据白送。网络上有大量自动化扫描脚本专门找6379端口没密码的Redis拿到之后可以直接写crontab挖矿这类事故经常见。后端连接Redis时你在application.yml里配置的密码要跟这里一致。项目里如果用到Redis做分布式锁还要注意同一个缓存key要避免被多个环境变量污染。我在部署时习惯给每个环境的key加前缀比如dev:token:、prod:token:方便排查。3.3 MinIO对象存储部署上门服务App一定离不开图片。用户要传户型图、师傅要传完工照、后台要存资质文件。如果直接把图片传到业务服务器磁盘问题很多磁盘空间容易打满、多副本没法做、后续迁移麻烦。用MinIO自建对象存储是目前中小项目性价比最高的方案。services: minio: image: minio/minio:latest container_name: app-minio restart: always command: server /data --console-address :9001 environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: MinioAdmin123 ports: - 9000:9000 - 9001:9001 volumes: - ./data/minio:/data启动后访问http://服务器IP:9001用控制台创建bucket。一般我会建三个bucketavatar、order-image、certificate。后端配置时把endpoint填成http://127.0.0.1:9000或者你内网容器间访问的地址。对外访问图片则走Nginx反向代理不要把MinIO端口直接暴露到公网否则你需要额外处理证书和防盗链运维成本会上升。3.4 Spring Boot后端参数配置与启动后端部署的本质就是把jar包放进容器然后让它连上MySQL、Redis、MinIO这三个依赖。如果你源码里有application-dev.yml和application-prod.yml两套配置部署时记得指定使用prod否则它会默认用开发配置连接本地数据库。我给出的Spring Boot配置片段可以当模板server: port: 8080 spring: datasource: url: jdbc:mysql://mysql:3306/door_service?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: doorservice password: ServicePassw0rd redis: host: redis port: 6379 password: RedisPassw0rd minio: endpoint: http://minio:9000 access-key: minioadmin secret-key: MinioAdmin123 bucket: order-image上面出现的mysql、redis、minio不是字符串而是Docker Compose网络里的服务名。只要所有服务都在同一个docker-compose.yml里容器互相之间直接用服务名访问即可不需要写死IP。打包后的执行命令一般是这样cd server mvn clean package -DskipTests docker build -t door-server .Dockerfile里核心内容无非是“把jar包复制到容器并启动”。基础镜像可以用openjdk:17-jdk-slimJVM参数在启动时设置java -Xmx1024m -Xms512m -jar app.jar --spring.profiles.activeprod-Xmx1024m意思是最大堆内存1G对小项目够了。如果你服务器只有2G内存还跑着MySQL和Redis堆上限不要超过1G否则会频繁触发系统内存回收甚至整机卡死。3.5 文件上传与静态资源访问的关系很多人部署成功后发现App能请求接口但图片加载不出来。问题多半出在文件上传成功但访问路径不对。MinIO存好文件后返回的URL往往是http://127.0.0.1:9000/order-image/xxx.jpg这个地址只有服务器本机能访问手机不可能访问。正确做法是后端配置MinIO的访问域名或路径为Nginx代理后的公网地址例如https://api.yourdomain.com/minio/order-image/xxx.jpg。在Nginx里把这个路径指向MinIO服务既统一了访问入口又可以不暴露9000端口。同时要注意上传文件大小限制。Spring Boot默认限制1MB同城上门服务里师傅拍完工照通常有几张高清图1MB肯定不够。要在配置里加上spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MBNginx那边也要同步放行否则Nginx会先拦截掉大文件。后面我会给出Nginx的配置示例。4. App端打包与第三方能力接入4.1 uniapp前端到原生包的流程同城上门服务App的用户端和师傅端我建议用uniapp打包。打包流程一般是在HBuilderX里打开项目修改manifest.json中的AppID、应用名称和图标然后选择“云打包”生成apk或ipa。如果你习惯用命令行也可以用dcloudio/uvm工具。但云打包需要联网并且iOS打包需要证书和描述文件。个人开发阶段不着急上架可以先打自定义调试基座真机跑通业务。正式上架前再打正式包。打包之前必须把manifest.json里的模块配置好。常见的几项包括地图模块选高德地图或腾讯地图需要自己在对应开放平台申请Key。定位模块与地图选择的Key绑定。推送模块选择极光推送、个推或腾讯TPNS。支付模块微信支付、支付宝支付需要填写对应的AppID和密钥。这里有一个很容易踩的坑uniapp云打包时如果你只勾选了地图模块但没填Key打包过程通常不会报错但运行时一调用地图就闪退。所以打包之前要先把第三方平台的账号、Key都准备好。4.2 推送、地图、支付等第三方SDK配置上门服务的时效性很强所以消息推送很关键。用户在App里下单师傅端要立刻收到“新订单”推送师傅接单后用户端也要收到通知。推送方案的选型也很重要。如果只是少量用户用uniapp自带的plus.push就好。但生产环境我建议接入产品化推送服务比如极光推送或个推因为它们有厂商通道。简单说OPPO、vivo、小米、华为各自都有系统级推送通道App没打开时也能收到通知。第三方推送可以把消息通过厂商通道送达否则App进程被系统杀死后就收不到推送这对上门服务App来说简直致命。地图和定位配置相对直白但是要注意定位权限的说明文案。iOS上如果NSLocationWhenInUseUsageDescription没写清楚系统会直接拒绝权限申请甚至审核时会被拒绝。Android高版本也需要动态申请权限uniapp的权限配置在manifest.json里App权限配置下勾选。支付环节微信支付和支付宝的配置最容易出问题。回调地址必须是公网可访问的HTTPS地址且不能带本地端口号。后端收到支付回调后需要先验签再更新订单状态。如果回调地址配置不对用户付了钱订单却一直是“待支付”这种事故非常影响口碑。4.3 前端如何配置不同环境的API地址开发时App在本地局域网连http://192.168.x.x:8080没问题但打包后装在用户手机里必须访问公网HTTPS地址。所以前端工程里通常会有一个config.js用来管理环境const ENV { baseUrl: https://api.yourdomain.com, timeout: 15000, uploadUrl: https://api.yourdomain.com/api/upload } export default ENV打包前把这个文件改成公网域名即可。如果你用的是uni.request还需要在后台配置“安全域名”否则在微信小程序端会拦截请求。原生App则不需要小程序那种域名白名单但必须解决Android明文流量的问题。Android 9及以上默认禁止HTTP明文请求如果你的域名还是http://而不是https://App会直接报错。解决方案就是在AndroidManifest.xml里加android:usesCleartextTraffictrue但这只是临时开发手段正式环境一定要配好HTTPS。iOS同样有ATS限制不允许HTTP请求。所以生产环境的API域名必须上HTTPS这就是为什么后面Nginx和SSL证书是重头戏。5. 上线部署HTTPS、反向代理与运维监控5.1 Nginx反向代理与SSL证书到了这一步后端和App都准备好了但还不能直接对用户开放。你需要一个统一入口把不同服务路由到不同后端同时把HTTP升级成HTTPS。Nginx就是干这个的。先看一个完整的服务配置示例server { listen 80; server_name api.yourdomain.com; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name api.yourdomain.com; ssl_certificate /etc/nginx/ssl/api.yourdomain.com.pem; ssl_certificate_key /etc/nginx/ssl/api.yourdomain.com.key; client_max_body_size 20m; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location /minio/ { proxy_pass http://127.0.0.1:9000/minio/; proxy_set_header Host $host; } }这里最关键的是proxy_set_header三件套尤其X-Forwarded-For和X-Forwarded-Proto。如果不设置后端拿到的用户IP永远是Nginx本机IP导致无法按城市、地区做派单而且如果后端生成某些跳转链接时会因为没识别到HTTPS而生成http://开头的地址客户端调用就失效。client_max_body_size一定要设置成和后端配合的值。我遇到过用户上传20MB的完工视频Nginx默认1MB直接返回413错误排查了半天才发现是这个配置项。SSL证书用免费版就够国内的话可以在云服务商控制台申请免费证书也可以使用acme.sh配合Lets Encrypt自动续期。记住证书下发后一定要在到期前一个月检查不然证书失效导致的App全量报错会让你非常被动。如果你想同时部署管理后台也可以在上面的Nginx配置里增加第二个server块把admin.yourdomain.com指向Vue打包后的dist目录server { listen 443 ssl; server_name admin.yourdomain.com; root /opt/project/admin/dist; index index.html; location / { try_files $uri $uri/ /index.html; } }5.2 消息队列与定时任务订单超时与派单延迟同城上门服务App天然依赖“时效性”。用户下了一个保洁订单30分钟内没人接单系统应该自动取消或提醒客服介入师傅接单后10分钟没有出发后台要提醒师傅。这些需求如果用数据库轮询来实现既浪费资源又不够实时。所以我建议在部署阶段就把延迟消息机制准备好。轻量方案是使用Redis的Key过期事件或者延迟队列但生产环境我更推荐引入RabbitMQ或者RocketMQ。不是因为它们更“高级”而是因为它们有专门的消息TTL和延迟交换机可以实现精确到秒的延时任务。部署上加一个rabbitmq容器即可services: rabbitmq: image: rabbitmq:3.12-management-alpine container_name: app-rabbit restart: always environment: RABBITMQ_DEFAULT_USER: rabbituser RABBITMQ_DEFAULT_PASS: RabbitPassw0rd ports: - 5672:5672 - 15672:15672后端在创建订单时往延迟队列里塞一条“订单超时检查”的消息。等延迟时间到了消费者去查订单状态如果还在“待接单”就把订单关闭并通知用户。这套机制部署简单但需要确认源码里是否已经集成了对应的消息队列客户端。如果源码没有这层逻辑也可以先把Redis过期事件作为过渡。5.3 日志收集与异常告警部署完成不等于万事大吉。App上了线用户一多各种异常就来了数据库连接超时、第三方接口返回异常、订单状态不更新。这时候如果没有日志和告警你会像无头苍蝇一样。我的习惯是后端在本地日志目录的logs/app.log之外同步把ERROR级别的日志发送到钉钉机器人或者企业微信机器人。做法不复杂后端里加一个Logback的WebhookAppender或者写一个简单的Shell脚本定时扫日志。如果你不想改代码用Prometheus Grafana那套监控体系更标准但配置成本高。简单方案在服务器上部署一个Watchtower或者Supervisor来保证后端进程挂掉后自动重启。Docker使用restart: always策略已经能解决大部分“进程崩溃”问题。但对于更隐蔽的内存泄漏、慢SQL、接口响应时间变长你还是需要一个“健康检查接口”。后端如果有/actuator/health你可以在Nginx配置里加一条监控规则用crontab每分钟请求一次返回非200就发告警。我自己实测这个做法比很多重型监控工具更直接有效。5.4 数据备份与容灾恢复前面你可以不重视但数据备份必须重视。同城上门服务的订单数据、用户余额、流水记录一旦丢失就是事故。我的备份策略是每天凌晨全量备份MySQL数据。每6小时备份一次增量binlog日志。对象存储中的图片和资质文件每天同步一次到本地磁盘再打包上传。MySQL备份命令很简单docker exec app-mysql sh -c exec mysqldump -u root -p$MYSQL_ROOT_PASSWORD --databases door_service --single-transaction backup.sql加--single-transaction可以在不锁表的情况下备份InnoDB表避免线上服务在备份期间出现短暂卡顿。配合crontab0 3 * * * /opt/backup/backup.sh备份脚本运行时如果磁盘是同一块云硬盘注意别把备份放在数据库容器目录内那样容器文件一误删备份也一起没了。我通常放到独立的/data/backup目录然后定期传到另一个存储分区。恢复时先停服务再用mysql -u root -p door_service backup.sql导回去。恢复后检查关键表的数据量和最新订单时间。别以为“备份安全”没有恢复演练过的备份都不算真正可用。我建议上线后第一个月就演练一次完整恢复真到要用的时候你才敢按下回车。6. 上线后的常见问题与排错速查6.1 App连不上服务器先查这五个地方部署上线后最多人问“为什么App请求失败”。这个问题从发生的概率来说十次有八次不是代码问题。按下面的顺序排查基本能定位服务器安全组是否放行端口云服务器控制台的安全组和系统防火墙ufw都要同时验证。App请求的域名能解析到服务器IPping api.yourdomain.com或nslookup确认。SSL证书是否有效用浏览器访问https://api.yourdomain.com看证书有没有告警。Nginx是否成功转发登录服务器curl -k https://api.yourdomain.com/api/health。后端日志有没有报错直接看logs/app.log的ERROR。如果以上都正常再考虑客户端问题。Android客户端记得检查usesCleartextTrafficiOS检查ATS。开发模式下可以用手机浏览器直接访问接口地址如果能通说明服务端和网络都没问题问题大概率在App打包配置里。6.2 订单状态不一致回调和幂等是关键上线后最容易出现的业务问题是支付成功但订单还是“待支付”。一般原因是支付回调没有正确处理。支付宝、微信支付成功后会主动请求后台配置的notify_url。如果这个地址被墙了、证书不被信任、或者后端处理回调时抛了异常那订单状态就一直变不了。处理方式其实就三板斧验签、改单、重复回调不报错。我实际排查过的一个案例是微信回调请求到达Nginx后Nginx返回504因为后端在验签时调用了外部接口外部接口超时导致整体响应超时。解决方法是完成验签后立即返回成功响应把后续的订单状态更新放到消息队列里异步处理。这样即使后面积压了任务微信那边也已经收到了正确的回调响应不会反复重发。6.3 高峰期订单量上来后服务变慢如果上线后某天流量突然涨了后端响应开始变慢大概率不是代码突然不行了而是数据库和Redis连接池被打满。先看MySQL连接数SHOW STATUS LIKE Threads_connected;如果连接数接近max_connections去application.yml里把数据库连接池HikariCP的maximum-pool-size调大同时调整MySQL的max_connections。另外还有一招用Redis缓存热数据。比如用户打开App首页看到的是服务分类和推荐服务这些数据短期不会变化可以缓存到Redis里缓存命中率上去了数据库压力自然降下来。我实测一个有效做法是把App首页接口的响应时间从400ms降到80ms只做了两步加Redis缓存去掉接口里的非必要字段。上线高峰期应用性能优化第一步永远是减少数据库重复查询。6.4 后端启动失败从日志和端口入手部署过程中段错误很常见。比如你改了数据库密码忘记改后端配置文件里对应的密码Spring Boot启动就会报“Cannot create PoolableConnectionException”。还有一些情况是端口占用8080被其他进程占用了后端启动就失败。排查启动问题最直接的方法是把日志完整看一遍而不是只看报错那一行。后端常见的启动失败原因可以列成一张表现象常见原因解决办法数据库连接失败密码、地址、端口配置错误检查application-prod.yml确认Docker容器是否在运行Redis连接超时Redis未启动或者密码错误在服务器本地连接Redis验证密码内存溢出JVM堆内存配置过大调整-Xms和-Xmx留足系统内存端口被占用8080端口被其他服务占用改为其他端口或结束占用进程上传文件失败MinIO连接失败或bucket不存在检查MinIO服务日志创建bucket排错的核心原则是先确认为什么再决定怎么改。很多人在部署阶段遇到问题喜欢直接“换配置文件”或者“重启”但如果你不看清日志问题会反复出现。7. 部署中那些让我印象深刻的细节到这里全栈部署的主干流程已经完整走完了。最后我再补充几个个人经验和容易忽略的小细节这些细节在常规文档里通常看不到但非常影响线上体验。第一后端部署要使用“优雅停机”。Spring Boot默认停机方式是直接关闭进程正在处理的订单请求可能中断。生产环境建议开启server.shutdowngraceful并配置一个合理的超时时间server: shutdown: graceful spring: lifecycle: timeout-per-shutdown-phase: 30s这样你发布新版本时Nginx先不下线等旧进程处理完手头请求再退出。我见过有团队每次发版都在掉订单就是因为忽略了这一点。第二环境变量和密钥要分开管理。不要把所有密码都写在application.yml里更不要提交到Git仓库。用Docker Compose时可以把环境变量放在.env文件里并把这个文件从版本控制中排除。我见过不止一次有人把微信支付商户密钥、数据库密码直接写在源码里然后整个仓库公开在Gitee上基本等于把资金账号暴露了。第三App版本升级策略要想好。同城上门服务App不像纯内容App用户不需要频繁更新也能用。但一旦后端加了新功能旧版本App可能因为接口不兼容而报错。所以在后端设计接口时就要有版本号比如/api/v1/order和/api/v2/order并存一段时间给用户留出升级缓冲。很多项目死在“后端一改App就废”的泥潭里根源就是没规划好接口兼容。第四师傅端用户很少会去应用商店主动搜你的App所以二维码下载和分享链接很重要。我部署时单独做了一个下载页通过Nginx托管apk文件和下载二维码并在包名、签名、图标上做到和正式版一致否则用户更新App时系统会提示签名不一致无法覆盖安装。第五别忽略后台管理端的使用体验。很多团队把精力花在两个App上管理后台做得极其简陋结果运营人员连订单改价都找不到入口。管理后台是整个业务的中枢上线前一定要让真正运营的人测试一遍而不是让开发自己觉得“能用就行”。这套方案做完之后你会发现整个系统并不难难的是细节之间的配合。数据库字符集会影响表情写入Nginx转发头会影响IP定位支付回调的异步处理会影响订单状态App打包签名会影响更新。任何一个环节断了用户体验都会受损。我希望这篇详解能帮你把部署路上的暗坑提前填平让你把更多精力放在业务推广和订单增长上而不是整天困在服务器和代码里。