云服务器传code全链路解析:ssh、scp、unzip协同实战

1. 项目概述:云服务器传 code 文件这件事,远不止敲一行 scp 那么简单

“云服务器传 code 文件等内容”——这行标题看着平平无奇,像极了新手在技术群里的第一句提问。但如果你真在凌晨两点卡在部署环节,对着终端里反复报错的scp: connection refusedfailed to unzip发呆,就会明白:这根本不是“传个文件”这么轻巧的事,而是一整套横跨本地开发环境、网络链路、远程系统权限、压缩包完整性、执行上下文的协同工程。我做过上百次 SpringBoot 项目从本地 IDE 到阿里云 ECS 的完整交付,也帮客户排查过腾讯云 CentOS7.9 上unzip命令静默失败的底层编码问题;这些经验告诉我,真正卡住人的,从来不是某个命令记不熟,而是对每个环节背后“为什么必须这样”的无知。本文聚焦的,就是把“传 code”这件事彻底拆开:从你右键点击 VSCode 的“Remote-SSH 连接”那一刻起,到最终java -jar app.jar成功监听 8080 端口,中间所有隐性依赖、常见断点、参数陷阱和实操心法。核心关键词云服务器、code、scp、ssh、unzip不是孤立工具名,而是五个咬合紧密的齿轮——ssh 是通道的锁芯,scp 是通道上的货运列车,code 是货物本身,unzip 是卸货时的开箱工具,而云服务器,是那座你从未真正看清全貌的仓库建筑。适合三类人:刚买完阿里云学生机想部署第一个项目的大学生、用着 Docker 却搞不清基础文件传输逻辑的初级运维、以及被“导入资源包失败 caused by: invalid zip archive”这类报错反复折磨的前端工程师。这不是命令速查表,而是一份带着体温的操作日志。

2. 整体设计思路:为什么不用 rsync?为什么绕不开 ssh 密钥?为什么 unzip 在不同系统上会“装死”?

2.1 传输方案选型:scp 是最朴素的“安全底线”,不是最优解但绝对不可跳过

很多人一上来就想用rsync或 VSCode 的 Remote-SSH 自动同步,甚至直接拉 Git 仓库。但我要先泼一盆冷水:在首次建立可信链路前,scp 是唯一值得你花十分钟认真配置的方案。原因有三:
第一,零依赖性scp是 OpenSSH 套件的原生组件,只要云服务器装了openssh-server(所有主流发行版默认安装),无需额外装rsyncinotify-tools或 VSCode 插件。我见过太多人在 Ubuntu 22.04 上rsyncbash: rsync: command not found,最后发现连rsync包都没装——而scp永远在/usr/bin/scp下安静待命。
第二,语义明确,错误直白scp -r ./src user@ip:/home/user/app/这条命令,含义清晰到小学生都能看懂:把本地./src目录递归复制到远程user用户家目录下的app文件夹。而rsync -avz --delete的参数组合,光-a就包含-rlptgoD七个子开关,新手调错一个就可能删掉远程整个目录。更关键的是,scp的报错如Permission denied (publickey)No route to host,直接指向问题根源;rsyncconnection unexpectedly closed却常让人误以为是网络问题,实际只是远程 shell 启动脚本里写了exit 1
第三,它是所有高级方案的基石。VSCode 的 Remote-SSH 扩展底层调用的就是scp做文件上传;Docker 构建时COPY . /app的源文件,最初也是靠scp传上去的。跳过scp直接上高级工具,就像没学过加减法就去解微分方程——表面快,内里全是空中楼阁。

2.2 为什么必须配 ssh 密钥?密码登录是生产环境的“自杀式操作”

看到热搜词里反复出现ssh 免输入密码 vscodegit配置ssh密钥,就知道这是个高频痛点。但很多人只知其然,不知其所以然:免密码不是为了省事,而是为了切断攻击面
云服务器的 SSH 默认端口 22 是互联网扫描器的头号目标。暴力破解密码的脚本每秒可尝试上千次组合,而你的root密码哪怕设成P@ssw0rd2024!,在算力面前也撑不过 3 小时。密钥认证则完全不同:它基于非对称加密,私钥永远留在你本地电脑,公钥放在服务器~/.ssh/authorized_keys中。登录时,服务器用公钥加密一段随机数据发给你,你用私钥解密后返回结果——整个过程不传输密码,也不暴露私钥。即使攻击者拿到公钥,也无法反向推导私钥(RSA-2048 的暴力破解需超宇宙年龄)。
实操中,我坚持两个铁律:

  • 绝不使用密码登录 root 账户。创建普通用户(如deploy),用sudo usermod -aG sudo deploy加入管理员组,再禁用 root 密码:sudo passwd -l root
  • 私钥必须加密码保护。生成时用ssh-keygen -t ed25519 -C "your_email@example.com",过程中务必输入 passphrase。有人嫌麻烦,结果私钥文件被盗,等于把服务器大门钥匙交给了黑客。我的经验是:用 macOS 的 Keychain 或 Windows 的 OpenSSH Agent 缓存 passphrase,输一次即可全天免密,安全与便捷兼得。

2.3 unzip 的“隐形战争”:为什么 CentOS7.9 要手动编译,而 Ubuntu 22.04 却总报编码错误?

热搜词里unzip下载 centos7.9ubuntu unzip并列出现,绝非偶然。unzip这个看似简单的工具,在不同发行版上藏着巨大的兼容性陷阱。
先说 CentOS7.9:官方源的unzip版本是 6.0(2012 年发布),而现代 Java 项目打包的 JAR/WAR 文件普遍使用 ZIP64 扩展和 UTF-8 文件名。老版unzip解压时会把中文路径变成乱码,甚至直接跳过文件。解决方案不是yum install unzip,而是源码编译新版

# 下载 unzip 6.0 之后的版本(如 6.1) wget https://sourceforge.net/projects/infozip/files/UnZip%206.x%20%28latest%29/UnZip%206.0/unzip60.tar.gz tar -xzf unzip60.tar.gz cd unzip make -f unix/Makefile generic sudo make -f unix/Makefile install

这个过程耗时约 3 分钟,但能一劳永逸解决invalid zip archive错误。

再说 Ubuntu 22.04:它自带unzip 6.0,但问题出在 locale 设置。当服务器LANG=C(纯 ASCII 环境)时,unzip会按 CP437 编码解压文件名,而你的 Windows/Mac 打包工具用的是 UTF-8,结果就是caused by: invalid zip archive: coul(实际是could not被截断)。修复只需一行:

# 永久生效(写入 /etc/environment) echo 'LANG=en_US.UTF-8' | sudo tee -a /etc/environment # 或临时生效(当前会话) export LANG=en_US.UTF-8

提示:判断是否为编码问题,用unzip -l your.zip查看列表。如果文件名显示为??.java或乱码,基本可锁定为 locale 问题。

3. 核心细节解析:从本地 code 到云服务器落地的七道关卡

3.1 关卡一:code 文件的“纯净度”检查——你传的真是能跑的代码吗?

很多部署失败,根源不在服务器,而在你本地的code本身。我见过三次类似案例:开发者把整个 IDEA 项目目录(含.idea/target/node_modules/)打包上传,结果unzip后磁盘爆满,或java -jar因找不到主类失败。真正的“code 内容”必须满足三个硬性条件

  • 结构干净:SpringBoot 项目只传target/your-app.jar(Maven 构建后)或dist/目录(Vue 项目);Python Flask 只传app.py+requirements.txt;Node.js 只传package.json+index.js+node_modules/(若未用 Docker)。
  • 依赖明确:检查pom.xmlpackage.json中的版本号是否与服务器环境匹配。例如,你在本地用 JDK17 开发,却往只装了 OpenJDK8 的 CentOS7 上传jarjava -version一查就露馅。我的做法是:在pom.xml中强制指定<java.version>11</java.version>,并在服务器java -version输出后才开始传输。
  • 路径可移植:避免硬编码绝对路径。比如File file = new File("/Users/xxx/data.txt")在服务器上必然报错。应改用相对路径new File("data.txt")或通过 JVM 参数传入:java -Ddata.path=/home/deploy/data -jar app.jar

实操技巧:用tree -L 2 -I "node_modules|.git|.idea|target"命令快速预览要传的目录结构,确保只包含必要文件。曾有个客户传了 2GB 的node_modules,结果scp传了 40 分钟,最后发现服务器内存不足,npm install直接 OOM——其实npm ci --only=production就能解决。

3.2 关卡二:scp 命令的“五维参数矩阵”——每个字母都在说话

scp看似简单,但参数组合构成一张精密的控制网。热搜词里scp -r -pscp -p频繁出现,说明很多人只知皮毛。我们来拆解scp的核心参数如何影响传输结果:

参数作用必须性实操风险
-r递归复制目录传整个项目时必需若目标目录已存在同名文件,会覆盖而非合并
-p保留文件权限、时间戳、属主强烈推荐若不加,.sh脚本传过去后chmod +x才能执行,增加步骤
-C启用压缩传输大文件(>10MB)时推荐对小文件反而增加 CPU 开销,降低速度
-i ~/.ssh/id_ed25519指定私钥文件使用密钥认证时必需路径错误直接报Permission denied,需用ls -l ~/.ssh/确认权限为600
-o StrictHostKeyChecking=no跳过主机密钥确认自动化脚本中必需仅限内网或可信环境,公网服务器禁用,否则易受中间人攻击

一个典型的安全命令是:

scp -rpC -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no ./springboot-app.jar deploy@123.56.78.90:/home/deploy/app/

这里-p保证app.jar的执行权限保留;-C对 JAR 这种高压缩率文件提速 30%;StrictHostKeyChecking=no避免首次连接时交互式确认(自动化部署必备)。

注意:-p保留的是本地文件的权限。如果本地app.jar权限是644(不可执行),传过去还是644。Java JAR 本身不需要执行权限,但如果是启动脚本start.sh,就必须chmod +x start.sh再传,或传完立刻ssh deploy@ip 'chmod +x /home/deploy/app/start.sh'

3.3 关卡三:云服务器的“接收区”规划——别让文件散落在/root下吃灰

传文件不是终点,而是起点。我见过太多人scp完就ssh进去ls,发现文件在/root下,然后cd /rootjava -jar app.jar,结果因权限问题无法写日志,或端口被占用。科学的接收区规划有三个层级

  • 一级隔离:专用用户与目录
    创建deploy用户(非 root),所有代码、配置、日志都归属该用户。目录结构固定为:

    /home/deploy/ ├── app/ # 主程序 jar/war 文件 ├── config/ # 外部配置文件(application.yml) ├── logs/ # 日志输出目录(需 `chmod 755`) └── scripts/ # 启动/停止脚本(start.sh, stop.sh)

    这样做的好处是:sudo systemctl管理服务时,能精准控制用户权限,避免root运行带来的安全风险。

  • 二级防护:SELinux 与防火墙放行
    阿里云 CentOS7.9 默认开启 SELinux,它会阻止 Java 进程绑定 8080 端口,报错java.net.BindException: Permission denied。解决方案不是关 SELinux(setenforce 0),而是打标签:

    # 允许 java 绑定 8080-8089 端口 sudo semanage port -a -t http_port_t -p tcp 8080 # 查看当前端口策略 sudo semanage port -l | grep http_port_t

    同时,云服务器安全组必须放行对应端口。很多人只记得iptables,却忘了阿里云控制台的“安全组规则”才是第一道墙。

  • 三级兜底:磁盘空间与 inode 预警
    scp传大文件前,先df -h看磁盘,df -i看 inode。曾有个客户传了 5000 个微小配置文件,df -h显示还有 20GB,但df -i显示 inode 使用率 99%,导致unzip时提示No space left on device。解决方案:清理/tmp下旧文件,或用find /home/deploy/logs -name "*.log" -mtime +7 -delete定期删除 7 天前日志。

3.4 关卡四:unzip 的“解压仪式感”——三步验证法确保万无一失

unzip不是按下回车就完事的魔法。我总结了一套“三步验证法”,每次解压必走流程:
第一步:校验压缩包完整性

# 检查 ZIP 结构是否损坏 unzip -t your-code.zip # 输出 "No errors detected in compressed data of your-code.zip" 才算过关

如果报bad CRCinvalid zip archive,说明传输过程中文件损坏。此时不要重试unzip,而应重新scp——因为scp本身有 MD5 校验,传坏的概率极低,大概率是本地源文件就有问题。

第二步:预览内容结构

# 查看内部文件列表,确认路径、编码、大小 unzip -l your-code.zip | head -20 # 重点关注:是否有中文路径?最大文件是否超 2GB?(老版 unzip 不支持 ZIP64)

如果看到???.java,立即停手,按前文方法修复 locale;如果最大文件标着4.2G,而你用的是 CentOS7.9 默认 unzip,必须升级。

第三步:带参数解压,拒绝默认行为

# 推荐命令:解压到指定目录,不覆盖已有文件,显示详细过程 unzip -o -d /home/deploy/app/ your-code.zip # -o 表示 overwrite,避免交互式询问 # -d 指定解压目录,防止解压到当前目录造成混乱

实操心得:永远不要在/home/deploy/app/目录下直接unzip your.zip。如果your.zip里有app/子目录,解压后会变成/home/deploy/app/app/,多一层嵌套。用-d明确指定目标,是职业习惯。

4. 实操全流程:以 SpringBoot 项目部署到阿里云 ECS 为例

4.1 准备工作:本地环境与云服务器的“双向体检”

在敲任何命令前,必须完成双方环境的基线确认。这不是形式主义,而是避免 80% 的“玄学错误”。

本地环境检查清单(Mac/Linux)

  • ssh -V输出OpenSSH_9.6p1或更高(低于 8.0 的版本有已知漏洞)
  • scp -V确认与 ssh 版本一致
  • tree命令可用(brew install treeapt install tree
  • 私钥文件~/.ssh/id_ed25519权限为600chmod 600 ~/.ssh/id_ed25519
  • 阿里云 ECS 的公网 IP 已复制,安全组已放行 22(SSH)和 8080(应用端口)

云服务器检查清单(CentOS7.9)

# 1. 确认 SSH 服务运行 sudo systemctl status sshd # 2. 确认 Java 环境(SpringBoot 需 JDK11+) java -version # 应输出 openjdk version "11.0.22" # 3. 确认 unzip 版本(必须 >=6.0) unzip -v | head -1 # 输出 UnZip 6.00 或更高 # 4. 创建部署用户并授权 sudo adduser deploy echo "deploy ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/deploy # 5. 初始化目录结构 sudo -u deploy mkdir -p /home/deploy/{app,config,logs,scripts}

注意:sudo -u deploy mkdir确保目录属主是deploy,避免后续chown。如果忘记这步,scp传过去的文件属主会是rootdeploy用户无法修改。

4.2 第一阶段:安全通道建立——从密码登录到密钥免密的完整迁移

这是整个流程的“信任锚点”。很多教程教你怎么生成密钥,却不说清楚迁移过程中的断点处理。

步骤 1:本地生成密钥对

# 生成 ed25519 类型密钥(比 RSA 更快更安全) ssh-keygen -t ed25519 -C "deploy@aliyun" -f ~/.ssh/id_ed25519 # 过程中输入 passphrase(如 123456),记住它!

步骤 2:将公钥上传到服务器

# 方法一:用 ssh-copy-id(最简单) ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@123.56.78.90 # 方法二:手动复制(当 ssh-copy-id 不可用时) cat ~/.ssh/id_ed25519.pub | ssh deploy@123.56.78.90 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

步骤 3:服务器端加固
编辑/etc/ssh/sshd_config

# 禁用密码登录(关键!) PasswordAuthentication no # 禁用 root 登录 PermitRootLogin no # 指定允许登录的用户 AllowUsers deploy

重启服务:sudo systemctl restart sshd

步骤 4:本地测试与故障排除

# 测试连接(应无需密码,且输入 passphrase 后直接登录) ssh -i ~/.ssh/id_ed25519 deploy@123.56.78.90 # 如果报错 "Permission denied (publickey)",按此顺序排查: # 1. 检查 ~/.ssh/id_ed25519 权限是否为 600 # 2. 检查服务器 ~/.ssh/authorized_keys 权限是否为 600,目录 ~/.ssh 权限是否为 700 # 3. 检查 /var/log/secure 日志:sudo tail -20 /var/log/secure | grep sshd

实操心得:ssh -v deploy@ip-v参数可输出详细调试日志,看到debug1: Offering public key: /Users/xxx/.ssh/id_ed25519表示客户端已发送密钥,若后续无响应,问题一定在服务器端配置。

4.3 第二阶段:code 传输与解压——从本地 target 目录到服务器 app 目录的精准投送

假设你的 SpringBoot 项目已用mvn clean package打包,生成target/demo-0.0.1-SNAPSHOT.jar

步骤 1:本地结构检查与压缩

# 进入项目根目录 cd /path/to/your/project # 创建纯净的部署包(不含 .git、IDE 文件) zip -r demo-deploy.zip target/demo-0.0.1-SNAPSHOT.jar \ --exclude "*.git*" \ --exclude "*.idea*" \ --exclude "target/*test*" # 检查 zip 大小 ls -lh demo-deploy.zip # 应在 10-50MB 区间,过大需检查是否误含 node_modules

步骤 2:scp 传输(带进度与压缩)

# 传输命令(注意路径斜杠结尾表示目录) scp -rpC -i ~/.ssh/id_ed25519 demo-deploy.zip deploy@123.56.78.90:/home/deploy/app/ # 传输完成后,服务器端验证 ssh deploy@123.56.78.90 "ls -lh /home/deploy/app/demo-deploy.zip"

步骤 3:服务器端解压与验证

# 登录服务器 ssh deploy@123.56.78.90 # 进入 app 目录 cd /home/deploy/app/ # 1. 校验 zip unzip -t demo-deploy.zip # 2. 预览内容 unzip -l demo-deploy.zip | head -10 # 3. 解压(覆盖旧文件,解压到当前目录) unzip -o demo-deploy.zip # 4. 重命名 jar 文件为统一名称(便于脚本管理) mv target/demo-0.0.1-SNAPSHOT.jar app.jar # 5. 设置执行权限(虽 jar 不需要,但习惯成自然) chmod 644 app.jar

步骤 4:配置分离与日志准备

# 创建外部配置文件 cat > /home/deploy/config/application.yml << 'EOF' server: port: 8080 spring: profiles: active: prod logging: file: name: /home/deploy/logs/app.log EOF # 创建日志目录并授权 mkdir -p /home/deploy/logs chmod 755 /home/deploy/logs

4.4 第三阶段:启动与守护——让 Java 进程在后台稳定运行

传完、解完、配完,最后一步是让它活下来。

步骤 1:手动启动测试

# 切换到 app 目录 cd /home/deploy/app/ # 用外部配置启动 java -jar app.jar --spring.config.location=file:/home/deploy/config/application.yml # 观察控制台输出,看到 "Started Application in X seconds" 即成功 # Ctrl+C 停止

步骤 2:编写 systemd 服务(推荐)
创建/etc/systemd/system/app.service

[Unit] Description=SpringBoot App Service After=network.target [Service] Type=simple User=deploy WorkingDirectory=/home/deploy/app ExecStart=/usr/bin/java -jar /home/deploy/app/app.jar --spring.config.location=file:/home/deploy/config/application.yml Restart=always RestartSec=10 StandardOutput=journal StandardError=journal SyslogIdentifier=app [Install] WantedBy=multi-user.target

启用服务:

sudo systemctl daemon-reload sudo systemctl enable app.service sudo systemctl start app.service # 查看状态 sudo systemctl status app.service # 查看日志 sudo journalctl -u app.service -f

步骤 3:防火墙与安全组最终确认

# CentOS7 防火墙放行 8080 sudo firewall-cmd --permanent --add-port=8080/tcp sudo firewall-cmd --reload # 阿里云控制台:安全组规则中添加入方向规则,端口范围 8080/8080,授权对象 0.0.0.0/0(或限定 IP)

此时,用浏览器访问http://123.56.78.90:8080,应看到 SpringBoot 默认页面。

5. 常见问题与排查技巧实录:那些让你抓狂的报错,其实都有迹可循

5.1 “ssh: could not resolve hostname d: name or service not known” —— 你少打了 @ 符号!

这个报错在热搜词里赫然在列,99% 的原因是命令写错了。新手常把scp ./code deploy@123.56.78.90:/path误写成scp ./code deploy123.56.78.90:/path(漏了@),或者scp ./code deploy@123.56.78.90:/path写成scp ./code deploy@123.56.78.90:/path(多了一个空格)。ssh解析时,把deploy123.56.78.90当成一个主机名,而 DNS 里当然查不到。
排查技巧

  • ping 123.56.78.90确认 IP 可达
  • ssh -v deploy@123.56.78.90看调试日志,找到debug1: Connecting to deploy123.56.78.90 [deploy123.56.78.90] port 22这一行,就能定位拼写错误

5.2 “failed to unzip 1: invalid zip archive: coul” —— 字符串截断暴露的深层问题

这个报错的后半截coul明显是could not的截断,说明unzip进程在读取 ZIP 头部时异常退出。根本原因有两个:

  • ZIP 文件损坏scp传输中断,或本地源文件不完整。用file your.zip检查,正常输出your.zip: Zip archive data, at least v2.0 to extract;若输出your.zip: data,说明不是 ZIP 文件。
  • 磁盘空间不足unzip解压时需要临时空间,通常是原文件大小的 1.5 倍。用df -h /home看剩余空间,若 < 1GB,先清理/tmp

终极验证法:在本地用unzip -t your.zip,若报错,问题在源文件;若本地正常,服务器报错,则是服务器环境问题(locale 或 unzip 版本)。

5.3 “ssh connection reset by peer” —— 不是网络问题,是服务器主动断连

这个报错常让人怀疑是阿里云网络抖动,实则 90% 是服务器端的sshd配置问题。sshd默认有连接超时机制,当客户端长时间无操作,会主动断开。

解决方案
编辑服务器/etc/ssh/sshd_config

# 客户端每 60 秒发一次保活包 ClientAliveInterval 60 # 连续 3 次无响应则断开 ClientAliveCountMax 3

重启sudo systemctl restart sshd

注意:ClientAliveInterval是服务器端配置,ServerAliveInterval是客户端配置(在~/.ssh/config中设置)。两者配合,才能彻底解决“连接突然断开”。

5.4 “vscode ssh 连接后无法上传文件” —— VSCode 的隐藏权限陷阱

VSCode Remote-SSH 扩展有时显示连接成功,但拖拽文件上传失败,报错EACCES: permission denied。这是因为 VSCode 的上传功能默认使用sftp协议,而sftp的根目录是用户家目录(/home/deploy),但你可能想传到/opt/app

解决方法

  • 在 VSCode 的 Remote-SSH 配置中,右键服务器连接 →Configure Server SettingsRemote.SSH: Default Path,改为/home/deploy/app
  • 或更彻底:在~/.ssh/config中为该服务器添加:
    Host my-aliyun HostName 123.56.78.90 User deploy IdentityFile ~/.ssh/id_ed25519 RemoteCommand cd /home/deploy/app
    这样每次连接都自动进入目标目录。

5.5 “导入资源包失败 caused by: 0: failed to unzip 1: invalid zip archive: coul” —— 前端工程师的专属噩梦

这个报错常见于 Vue/React 项目导入.zip资源包(如 UI 组件库)。根本原因在于:前端构建工具(如 Webpack/Vite)打包的 ZIP,内部文件名用了 UTF-8,而某些 Linux 系统的unzip默认用 CP437 解码

三步修复

  1. 在服务器执行locale,确认LANGen_US.UTF-8zh_CN.UTF-8
  2. 若不是,执行export LANG=en_US.UTF-8临时生效,或永久写入/etc/environment
  3. unzip -O UTF-8 your.zip强制指定编码解压(-O参数要求 unzip 6.0+)

实操心得:前端同学可以养成习惯,在打包 ZIP 前,用zip -r -Z store your.zip *-Z store表示不压缩,只打包),这样unzip解压时不会因压缩算法差异出错。

6. 进阶思考:当 scp 不再够用,下一步该走向何方?

做到上面所有步骤,你已经能稳定部署 95% 的项目。但技术演进不会停步,当团队规模扩大、项目增多、CI/CD 需求出现时,“手敲 scp” 就成了瓶颈。这时,你需要考虑三个跃迁方向:

6.1 方向一:从 scp 到 rsync —— 增量同步的效率革命

scp每次都是全量传输,而rsync只传差异部分。一个 100MB 的 SpringBoot 项目,改了一行代码,scp还是传 100MB,rsync可能只传 1KB。命令范式:

# 本地构建后,只同步变更文件 rsync -avz --delete -e "ssh -i ~/.ssh/id_ed25519" \ ./target/classes/ \ deploy@123.56.78.90:/home/deploy/app/classes/

--delete确保远程删除的文件本地也删,保持完全一致。但要注意:rsync--delete是危险操作,首次使用务必加--dry-run参数预览。

6.2 方向二:从手动部署到 CI/CD —— GitHub Actions 的 5 行自动化

当你有多个服务器,或需要测试通过后自动部署,就得拥抱 CI/CD。GitHub Actions 是最轻量的选择。在项目根目录创建.github/workflows/deploy.yml

name: Deploy to Aliyun on: [push] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build with Maven run: mvn clean package -DskipTests - name: Deploy via SCP uses: appleboy/scp-action@v0.1.7 with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} source: "target/*.jar" target: "/home/deploy/app/"

配置好 Secrets(HOST/USERNAME/KEY),每次git push,Actions 就自动构建、