臭名昭著aTrust隔离计划1.臭名昭著aTrust隔离计划2.VPN原理与aTrust隔离网络实践3.docker-easyconnect到底做了什么4.TUN(tunnel-隧道-虚拟网卡)模式
个人项目
personal(个人展示)GitHub Actions 自动部署个人展示项目
基于Quartz搭建的个人博客1.Quartz个人博客使用教程2.使用 rsync 增量部署 Quartz 博客3.使用 GitHub Actions 自动部署 Quartz 博客5.域名绑定6.CDN加速7.Google 搜索收录排查:为什么关键词搜得到别人,却搜不到我的网站?Obsidian、双向链接与知识图谱
架构设计
分布式分布式事务RPC原理
旅行
行程攻略青岛三日游行程攻略
前端
NginxNginx配置与反向代理入门
Nodenpm与npx的区别
数据库
mysql01.MySQL基础语法与约束02.MySQL数据库类型03.MySQL多表查询04.MySQL 索引05.MySQL 事务06.MySQL 隔离级别07.MySQL MVCC08.MySQL 锁09.EXPLAIN10.慢查询与 SQL 优化
mysql面试题01.索引问题02.事务与隔离级别03.锁机制04.存储引擎与性能优化05.用户权限、字符集与备份恢复
mysql实战01.EXPLAIN实战02.测试环境菜单误删排查与恢复03.MySQL IN 详解
网络
网络安全1.无线监听网卡苹果M系列虚拟机安装Kali Linux教程3.kali怎么使用RTL8812AU4.kali基础命令
网络基础进程之间怎样通过网络传递数据
网址常用网址
中间键
esElasticsearch 入门
mq
RabbitMQRabbitMQ高级RabbitMQ基础
1. MQ 基础2. MQ 核心角色与代码对应3. MQ Consumer Group 与消费模式4. SOFAMQ 账户变更代码实践5. MQ 可靠性问题6. 消息幂等
redis缓存击穿、穿透与雪崩Redis 分布式锁Redis 缓存问题Redis 数据结构
AI
使用说明
第三方插件&技能简介Archify使用与安装指南geekgeekRun使用OfferFlow使用指南Ponytail使用指南Skills Manager使用指南
CodexCodex CLI与IDE区别及使用指南Codex Hook单独配置与提交通知Codex MCP安装与使用指南Codex第三方插件安装与使用指南
Agent开发0-学习路线1.LLM、Token、上下文窗口与模型参数2.大模型 API、请求参数与响应结构3.Spring AI ChatClient4.Prompt、System Prompt、Prompt 模板6.结构化输出、JSON Schema7.SSE 流式响应8.会话 ID、聊天记录、Redis9.超时、重试、限流、降级10.*完成可运行聊天接口11.Function Calling、Tool Calling 原理12.用 Java 方法定义 Tool13.工具参数设计和校验14.实现订单查询工具15.实现物流查询工具16.实现库存查询工具17.多个工具自动选择18.工具异常、超时、重试
diy主机
主机零件学习1.CPU2.主板3.内存4.显卡5.硬盘6.散热7.风扇8.机箱热门型号价格表
捡垃圾diy-13600kf+5060diy-14600kf+5070
English高频词汇英文基础语法简单句子音标
GitGit常用命令Sublime Merge 使用指南
Java
工具库Caffeine本地缓存Reactor教程
工作实践如何使用@Async线程池使用与排查线上服务 CPU 使用率过高排查压测下RPC接口大量失败排查记录
基础1.HashMap2.ConcurrentHashMap3.HashSet4.final5.static6.volatile7.AtomicInteger8.String动态代理反射锁与线程同步线程注解
面试题1.HashMap 存储在方法内是线程安全的吗2.ArrayList参数传递与引用重新赋值3.== 和 equals 的区别、hashcode4.重载和重写的区别5.String、StringBuffer、StringBuilder区别及使用场景6.接口和抽象类7.ArrayList 与 LinkedList 的区别和扩容机制8.HashMap 和 Hashtable 的区别及底层实现9.Object 和 Objects 的区别10.ThreadLocal的原理和使用场景11.字节码是什么及作用12.Java类加载器有哪些13.双亲委派模型14.Java异常体系15.GC是什么16.GC如何判断对象可以被回收
面试总结0.HR面试模板1.面试一2.面试二3.面试三笔试
网络与 IO(黑马程序员-纯理论)01-网络编程基础02-IO模型与零拷贝03-BIO04-NIO基础与缓冲区05-NIO通道与选择器06-AIO
juc00-目录01-进程与线程02-线程同步与通信03-并发设计模式04-Java内存模型05-CAS与原子类06-ThreadLocal07-阻塞队列08-线程池使用与原理09-任务调度10-ForkJoin与享元模式11-AQS12-ReentrantLock13-读写锁14-并发协作工具15-ConcurrentHashMap16-CopyOnWrite与同步容器17-SkipListMap18-非阻塞队列
juc(工作常见)Java并发编程三个重点拆解
jvm1. JVM运行时数据区
Spring1.Spring IoC、DI与构造方法注入CGLIB、Byte Buddy 与 Spring AOPSpring 常用注解详解Spring Bean 生命周期
Spring MVC异常处理机制
linux常用命令
mac快捷键终端常用操作
python
基础01.Python是什么,为什么要安装Python02.Python项目中.venv是什么03.Python基础语法
基础实战sql替换
HTTPXHRMS员工详情接口调用(Python HTTPX)
windowswindows激活

1. GitHub Actions 是什么

把 GitHub Actions 想成 GitHub 在你每次推送代码后,临时帮你开的一台“自动干活的云电脑”。你不需要手动在 Mac 上构建、再登录服务器上传文件;它会按你写好的步骤自动完成。

本项目的实际过程是:

Mac 执行 git push origin main
            ↓
GitHub 临时启动一台 Ubuntu 云电脑
            ↓
按 .github/workflows/ 中的 YAML 步骤拉取代码、安装依赖、构建项目
            ↓
使用 SSH 私钥登录 47.99.178.186
            ↓
上传构建结果到 /www/personal/ 下的对应目录
            ↓
临时云电脑任务结束并被回收

这里的“临时云电脑”不是你的宝塔服务器。它只负责构建和上传;真正长期保存网站文件、运行后端的仍是 47.99.178.186。.github/workflows/ 里的 YAML 文件就是写给这台临时云电脑的操作清单。

2. 本次部署目标

仓库:yunyunafyke-sketch/personal
正式分支:main

模块本地构建目录服务器发布方式
frontend/frontend/dist/rsync 同步到作品集站点目录
admin/admin/dist/rsync 同步到独立管理站点目录
backend/backend/boot/target/boot-1.0.0-SNAPSHOT.jar上传 JAR 后重启 systemd 服务

本教程使用三份独立工作流。修改前台不会重启后端;修改后台不会重新构建前台。

3. 每一步在哪里操作

操作位置用来做什么本教程中对应的操作
Mac 本地终端生成密钥、获取服务器指纹、构建与提交代码ssh-keygen、ssh-keyscan、npm、mvn、git
宝塔的「终端」配置服务器 SSH、后端服务和 Nginx编辑 /root/.ssh/authorized_keys、执行 chmod、创建 systemd 服务
Mac 本地 IDE新建和编辑 GitHub Actions 工作流文件创建 .github/workflows/*.yml 并粘贴下文 YAML
GitHub 仓库网页保存密钥、查看或手动启动部署Settings → Secrets and variables → Actions、Actions

后文每个步骤都会标明操作位置。不要把 GitHub Secrets 写进代码文件,也不要在宝塔服务器中创建 .github/workflows/。

4. 本地提交前检查

在项目根目录 /Volumes/data/ideaWorkSpace/personal 执行:

cd /Volumes/data/ideaWorkSpace/personal
 
(cd frontend && npm ci && npm run build)
(cd admin && npm ci && npm run build)
(cd backend && mvn -B clean package)

首次配置前,先将项目推送到 GitHub 的 main 分支:

git add .
git commit -m "初始化个人展示项目"
git push -u origin main

node_modules/、dist/、target/、.idea/ 与 .vscode/ 不应提交;当前项目的 .gitignore 已包含这些规则。

5. 配置部署路径和 SSH 连接

5.1 确认发布路径

操作位置:宝塔的「终端」或文件管理器。

在 GitHub Secrets 和工作流中使用以下路径:

内容服务器路径
作品集前台静态文件/www/personal/frontend/
管理后台静态文件/www/personal/adminFrontend/
后端 JAR/www/personal/boot-1.0.0-SNAPSHOT.jar
后端日志/www/personal/logs/

前台和管理后台工作流分别同步各自的子目录;不要使用 --delete 同步 /www/personal/ 根目录,以免删除 JAR 或日志。

5.2 在 Mac 创建 GitHub Actions 专用密钥

本教程使用 root 登录服务器,因此 GitHub Secret DEPLOY_USER 填写 root。

操作位置:Mac 本地终端。不要把日常个人 SSH 私钥上传到 GitHub;执行以下命令新建专用密钥:

ssh-keygen -t ed25519 -C "github-actions-personal" -f ~/.ssh/personal_actions

这条命令会在当前 Mac 的 ~/.ssh/ 目录创建一对配套文件:

文件用途下一步放到哪里
personal_actions私钥,必须保密复制完整内容到 GitHub Secret SSH_PRIVATE_KEY
personal_actions.pub公钥,可以公开复制整行内容到服务器 /root/.ssh/authorized_keys

5.2.1 用门锁和钥匙理解私钥、公钥

把服务器的 root 登录想成一扇门:

现实中的东西SSH 中对应的文件放在哪里
门锁personal_actions.pub 公钥服务器 /root/.ssh/authorized_keys
唯一钥匙personal_actions 私钥GitHub Secret SSH_PRIVATE_KEY

部署时,GitHub Actions 拿着“唯一钥匙”敲服务器的门;服务器检查这把钥匙是否能匹配 authorized_keys 中保存的“门锁”。匹配成功,服务器才允许 GitHub Actions 以 root 身份登录并上传部署文件。

公钥可以放在服务器上,因为它只负责验证;私钥必须只由 GitHub 保存,因为拿到私钥的人就相当于拿到了这扇门的钥匙。因此,私钥不要上传到服务器、不要发给他人,也不要提交进项目仓库。

5.3 将私钥写入 GitHub

操作位置:先在 Mac 本地终端读取私钥,再在 GitHub 仓库网页保存。

在 Mac 本地终端执行:

cat ~/.ssh/personal_actions


复制输出的全部内容(包含 BEGIN OPENSSH PRIVATE KEY 与 END OPENSSH PRIVATE KEY 两行)。然后进入 GitHub 仓库 Settings → Secrets and variables → Actions,在 Repository secrets 区域点击 New repository secret,填写:

  1. Name:SSH_PRIVATE_KEY
  2. Secret:粘贴刚才复制的私钥完整内容
  3. 点击 Add secret 保存

私钥只写入 GitHub 的 Repository secret;不要写入服务器、项目代码、.env 文件或聊天记录。

5.4 将公钥写入服务器

操作位置:Mac 本地终端。执行下面命令查看公钥并复制输出的整行内容:

cat ~/.ssh/personal_actions.pub

操作位置:宝塔文件管理器或宝塔「终端」。将该公钥追加到服务器文件:

/root/.ssh/authorized_keys

5.5 设置 SSH 文件权限

操作位置:宝塔「终端」。设置 SSH 目录与公钥文件权限:

chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys

5.5.1 这两条权限命令的作用

这两条命令只修改“谁可以访问文件”的权限,不会删除密钥,也不会修改密钥内容:

命令实际效果
chmod 700 /root/.ssh只有 root 可以进入、查看和修改 .ssh 目录;其他服务器账号完全不能访问。
chmod 600 /root/.ssh/authorized_keys只有 root 可以查看和修改允许 SSH 登录的公钥列表;其他服务器账号不能读取或追加公钥。

这样做是为了防止其他服务器账号私自往 authorized_keys 中加入自己的公钥。SSH 会检查这两个权限;如果目录或文件对其他用户过于开放,SSH 可能直接拒绝使用其中的公钥,GitHub Actions 就会出现 Permission denied (publickey)。

5.6 获取服务器主机指纹

5.6.1 服务器主机指纹的作用

它用于让 GitHub Actions 确认“当前连接的确实是 47.99.178.186 这台服务器”。可以把它理解成服务器的身份证:部署时,服务器会出示自己的 SSH 身份信息,GitHub Actions 会与 SSH_KNOWN_HOSTS 中保存的记录核对;一致才会上传文件,不一致则停止连接。

5.6.2 用“送快递到正确地址”理解

把 GitHub Actions 想成送部署文件的快递员:

  1. SSH_PRIVATE_KEY 像快递员手里的开门钥匙,证明“快递员有权进门”。
  2. SSH_KNOWN_HOSTS 像寄件人提前交给快递员的“房屋身份证照片”,用来确认眼前这栋房子就是要去的那一栋。

即使快递员拿着正确钥匙,也不能只要看到一扇门就开门投递;它必须先核对门牌和房屋身份证。GitHub Actions 也是一样:先核对服务器身份,再用私钥登录。核对失败时宁可停止部署,也不会把网站文件上传到冒充 47.99.178.186 的其他服务器。

它与前面创建的部署密钥用途不同:

GitHub Secret验证谁作用
SSH_PRIVATE_KEYGitHub Actions证明 GitHub Actions 有权限登录服务器。
SSH_KNOWN_HOSTS服务器证明 GitHub Actions 连的是正确的服务器。

操作位置:Mac 本地终端。获取服务器主机指纹并保存:

ssh-keyscan -p 22 47.99.178.186 > /tmp/personal-known-hosts
cat /tmp/personal-known-hosts

将第二条命令输出的全部内容复制到 GitHub Secret SSH_KNOWN_HOSTS。

6. 创建后端 systemd 服务

6.1 systemd 是什么

systemd 可以理解成 Linux 服务器里一直值班的“应用管理员”。它不负责写 Java 代码,也不负责构建 JAR;它只负责按你给的说明启动后端、观察后端是否还活着,以及在服务器重启后重新启动后端。

如果只在宝塔终端中执行:

java -jar boot-1.0.0-SNAPSHOT.jar

后端和当前终端窗口绑得很紧:窗口关闭、程序异常退出或服务器重启后,通常需要你再手动启动。把后端交给 systemd 后,systemd 会按固定规则管理它:启动、异常后重试、开机自动启动和查看运行状态。

6.2 这一节最终要完成什么

/etc/systemd/system/personal-backend.service 是写给 systemd 的“后端运行说明书”。GitHub Actions 每次部署会覆盖 /www/personal/boot-1.0.0-SNAPSHOT.jar,然后远程执行 systemctl restart personal-backend。systemd 便会停止旧的 Java 后端进程,再用新 JAR 启动一个新的后端进程。

GitHub Actions 上传新的 JAR
            ↓
systemctl restart personal-backend
            ↓
systemd 停掉旧后端 → 按 service 文件启动新后端
            ↓
后端重新监听 9080 端口

6.3 service 文件每一部分的作用

配置白话作用
[Unit]服务的基本说明和启动前提。After=network.target 表示先让网络就绪,再启动后端。
[Service]后端程序实际怎么运行。
User=www用宝塔当前 Java 项目使用的 www 用户运行后端,保持目录和日志文件的原有权限习惯。
WorkingDirectory=/www/personal后端启动时所在的默认目录。
EnvironmentFile=/etc/personal-backend.env从这个服务器文件读取数据库密码、JWT、OSS 等敏感配置,不把它们写进 GitHub 或代码仓库。
ExecStart=...真正启动后端的命令。当前服务器没有全局 java,使用宝塔 JDK 路径 /www/server/java/jdk-17.0.8/bin/java。
Restart=always后端异常退出时,systemd 自动再次尝试启动。
RestartSec=5每次重试前等待 5 秒,避免失败时无限高速重启。
[Install] 与 WantedBy=multi-user.target允许将该服务设置为服务器开机后自动启动。

6.4 创建 service 文件

操作位置:宝塔「终端」。

在服务器创建 /etc/systemd/system/personal-backend.service:

[Unit]
Description=personal backend
After=network.target
 
[Service]
Type=simple
User=www
WorkingDirectory=/www/personal
EnvironmentFile=/etc/personal-backend.env
ExecStart=/www/server/java/jdk-17.0.8/bin/java -Xmx1024M -Xms256M -jar /www/personal/boot-1.0.0-SNAPSHOT.jar
Restart=always
RestartSec=5
 
[Install]
WantedBy=multi-user.target

创建 /etc/personal-backend.env,把数据库、JWT、Redis、阿里云 OSS 等敏感配置填在服务器中,不要写入仓库或 GitHub 工作流:

DB_USERNAME=数据库用户名
DB_PASSWORD=数据库密码
SERVER_PORT=9080
JWT_SECRET=至少32字节的随机密钥
ALIYUN_OSS_ACCESS_KEY_ID=你的AccessKeyId
ALIYUN_OSS_ACCESS_KEY_SECRET=你的AccessKeySecret
ALIYUN_OSS_BUCKET=personal-afyke

6.5 让 systemd 读取并启动后端

首次启动时在宝塔「终端」执行:

systemctl daemon-reload
systemctl enable --now personal-backend
systemctl status personal-backend
命令这一步在做什么
systemctl daemon-reload让 systemd 重新读取刚创建或修改过的 .service 文件。
systemctl enable --now personal-backend设置服务器以后开机自动启动,同时立刻启动当前服务。
systemctl status personal-backend查看后端是否成功运行;失败时在这里先看错误信息。

6.6 查看 systemd 后端日志

操作位置:宝塔「终端」。systemctl status 只显示少量最近日志;需要排查启动、接口或数据库问题时使用以下命令:

# 查看该服务目前保留的全部 systemd 日志
journalctl -u personal-backend --no-pager
 
# 查看最近 500 行
journalctl -u personal-backend -n 500 --no-pager
 
# 持续实时查看新日志;按 Ctrl+C 停止
journalctl -u personal-backend -f

6.7 从宝塔 Java 项目切换到 systemd

宝塔当前 Java 项目的“指定变量”与 /etc/personal-backend.env 是两套独立配置。切换时按以下顺序操作:

  1. 先将宝塔页面中现有的全部环境变量迁移到 /etc/personal-backend.env,但暂时不要删除宝塔项目。

  2. 在宝塔 Java 项目页面点击“停止”,让它释放 9080 端口;此时只停止,不删除配置,方便切换失败时恢复。

  3. 在宝塔「终端」执行:

    systemctl daemon-reload
    systemctl enable --now personal-backend
    systemctl status personal-backend
  4. systemctl status personal-backend 显示 active (running) 后,再打开现有管理后台并执行一次登录、查看项目或技术栈等会请求后端的操作。页面能正常返回数据,就表示“后端能正常访问”。

  5. 确认无误后,再删除宝塔 Java 项目的启动配置。

不要让宝塔 Java 项目和 systemd 同时运行:两者都会尝试占用 9080 端口,后启动的一方会启动失败。若 systemd 启动失败,先执行 systemctl disable --now personal-backend,再在宝塔页面重新启动原 Java 项目即可恢复。

本教程中 root 仅用于 GitHub Actions 通过 SSH 上传 JAR 和执行 systemctl;后端 Java 进程仍使用宝塔当前的 www 用户运行,这两件事不冲突。

7. 配置 Nginx

操作位置:宝塔「终端」或宝塔网站的 Nginx 配置编辑页。

前台域名根目录指向 /www/personal/frontend,管理后台使用单独子域名并指向 /www/personal/adminFrontend。两个站点均将 /api/ 转发给后端 9080 端口。

location / {
    try_files $uri $uri/ /index.html;
}
 
location /api/ {
    proxy_pass http://127.0.0.1:9080;
    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;
}

检查并加载配置:

nginx -t
systemctl reload nginx

8. 告诉 GitHub Actions 服务器在哪里、文件放哪里

操作位置:GitHub 仓库网页。

前面保存的私钥和服务器主机指纹,解决的是“能不能安全登录服务器”。这一节的内容则像交给 GitHub Actions 的一张部署地址单:服务器地址、登录端口,以及三种项目文件分别该放到哪个目录。

8.1 填写服务器地址和发布目录

再次点击 New repository secret,按下表逐项添加:

Secret 名称填写内容它在部署时做什么
DEPLOY_HOST服务器 IP 或域名告诉 GitHub Actions 要连接哪台服务器;这里填你的服务器 IP。
DEPLOY_PORTSSH 端口,例如 22告诉它从服务器的哪个 SSH 入口连接;你的服务器使用默认的 22。
DEPLOY_USERroot告诉它登录服务器时使用哪个账号;本项目用 root 上传部署文件。
FRONTEND_DEPLOY_PATH/www/personal/frontend前台网页构建完成后,上传到这个目录。
ADMIN_DEPLOY_PATH/www/personal/adminFrontend管理后台构建完成后,上传到这个目录。
BACKEND_DEPLOY_PATH/www/personal后端 JAR 上传到这个目录;最终文件是 /www/personal/boot-1.0.0-SNAPSHOT.jar。

Secret 保存后不能查看明文是正常的;只能覆盖更新。私钥、数据库密码和 JWT 密钥不能提交到 Git 仓库。

9. 在项目中创建工作流文件

操作位置:Mac 本地终端和 IDE。

在本地项目根目录执行:

cd /Volumes/data/ideaWorkSpace/personal
mkdir -p .github/workflows

然后在 IDE 的 .github/workflows/ 目录中分别新建以下三个文件:

deploy-frontend.yml  # 只发布 /www/personal/frontend
deploy-admin.yml     # 只发布 /www/personal/adminFrontend
deploy-backend.yml   # 只覆盖 /www/personal/boot-1.0.0-SNAPSHOT.jar 并重启服务

接下来按顺序将下面三段完整内容粘贴到对应文件。不要把工作流放在 frontend/、admin/ 或 backend/ 目录内;GitHub 只识别项目根目录 .github/workflows/ 下的 YAML 文件。

10. 创建前台部署工作流

创建 .github/workflows/deploy-frontend.yml:

name: 部署个人作品集前台
 
on:
  push:
    branches: [main]
    paths:
      - 'frontend/**'
      - '.github/workflows/deploy-frontend.yml'
  workflow_dispatch:
 
permissions:
  contents: read
 
concurrency:
  group: personal-frontend-production
  cancel-in-progress: true
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4
 
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
          cache-dependency-path: frontend/package-lock.json
 
      - name: 安装依赖并构建
        working-directory: frontend
        run: |
          npm ci
          npm run build
          test -f dist/index.html
 
      - name: 配置 SSH
        env:
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }}
        run: |
          install -m 700 -d ~/.ssh
          printf '%s\n' "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
          printf '%s\n' "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
          chmod 600 ~/.ssh/deploy_key ~/.ssh/known_hosts
 
      - name: 发布前台静态文件
        env:
          DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
          DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
          DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
          DEPLOY_PATH: ${{ secrets.FRONTEND_DEPLOY_PATH }}
        run: |
          rsync -az --delete \
            -e "ssh -i ~/.ssh/deploy_key -p ${DEPLOY_PORT} -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes" \
            frontend/dist/ \
            "${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/"

11. 创建管理后台部署工作流

创建 .github/workflows/deploy-admin.yml:

name: 部署个人展示管理后台
 
on:
  push:
    branches: [main]
    paths:
      - 'admin/**'
      - '.github/workflows/deploy-admin.yml'
  workflow_dispatch:
 
permissions:
  contents: read
 
concurrency:
  group: personal-admin-production
  cancel-in-progress: true
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4
 
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
          cache-dependency-path: admin/package-lock.json
 
      - name: 安装依赖并构建
        working-directory: admin
        run: |
          npm ci
          npm run build
          test -f dist/index.html
 
      - name: 配置 SSH
        env:
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }}
        run: |
          install -m 700 -d ~/.ssh
          printf '%s\n' "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
          printf '%s\n' "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
          chmod 600 ~/.ssh/deploy_key ~/.ssh/known_hosts
 
      - name: 发布管理后台静态文件
        env:
          DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
          DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
          DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
          DEPLOY_PATH: ${{ secrets.ADMIN_DEPLOY_PATH }}
        run: |
          rsync -az --delete \
            -e "ssh -i ~/.ssh/deploy_key -p ${DEPLOY_PORT} -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes" \
            admin/dist/ \
            "${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/"

12. 创建后端部署工作流

创建 .github/workflows/deploy-backend.yml:

name: 部署个人展示后端
 
on:
  push:
    branches: [main]
    paths:
      - 'backend/**'
      - '.github/workflows/deploy-backend.yml'
  workflow_dispatch:
 
permissions:
  contents: read
 
concurrency:
  group: personal-backend-production
  cancel-in-progress: true
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v4
 
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '17'
          cache: maven
 
      - name: 打包后端
        working-directory: backend
        run: |
          mvn -B clean package
          test -f boot/target/boot-1.0.0-SNAPSHOT.jar
 
      - name: 配置 SSH
        env:
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }}
        run: |
          install -m 700 -d ~/.ssh
          printf '%s\n' "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
          printf '%s\n' "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
          chmod 600 ~/.ssh/deploy_key ~/.ssh/known_hosts
 
      - name: 上传 JAR
        env:
          DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
          DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
          DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
          DEPLOY_PATH: ${{ secrets.BACKEND_DEPLOY_PATH }}
        run: |
          rsync -az \
            -e "ssh -i ~/.ssh/deploy_key -p ${DEPLOY_PORT} -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes" \
            backend/boot/target/boot-1.0.0-SNAPSHOT.jar \
            "${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/boot-1.0.0-SNAPSHOT.jar"
 
      - name: 重启并检查后端
        env:
          DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
          DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
          DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
        run: |
          ssh -i ~/.ssh/deploy_key -p "${DEPLOY_PORT}" \
            -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes \
            "${DEPLOY_USER}@${DEPLOY_HOST}" \
            'systemctl restart personal-backend && systemctl is-active --quiet personal-backend'

13. 提交并首次执行

操作位置:Mac 本地终端;随后在 GitHub Actions 页面查看执行结果。

将三份工作流提交到 main:

cd /Volumes/data/ideaWorkSpace/personal
git add .github/workflows
git commit -m "部署配置-新增 GitHub Actions 自动部署"
git push origin main

打开 GitHub 仓库的 Actions 页面,分别查看三个工作流。首次配置建议依次手动运行:前台、管理后台、后端;确认每一个都成功后,再进行日常开发。

14. 日常发布

操作位置:Mac 本地终端。

修改内容提交并推送后的动作
frontend/仅构建并发布作品集前台
admin/仅构建并发布管理后台
backend/仅构建、上传 JAR 并重启后端
git add frontend
git commit -m "个人展示-更新首页内容"
git push origin main

工作流文件本身修改后,推送到 main 同样会触发对应部署;也可以在 GitHub Actions 页面选择工作流后点击 Run workflow 手动执行。

15. 常见排查

现象处理方式
Permission denied (publickey)核对 SSH_PRIVATE_KEY、/root/.ssh/authorized_keys 和 .ssh 文件权限。
Host key verification failed重新获取并核对服务器主机密钥后,更新 SSH_KNOWN_HOSTS。不要关闭主机验证。
rsync 无法写入核对三个部署路径和 root 对目录的写入权限。
后端上传成功但未更新在服务器执行 systemctl status personal-backend 与 journalctl -u personal-backend -n 100。
管理后台刷新 404检查管理后台 Nginx 站点是否已配置 try_files $uri $uri/ /index.html。
前端请求 API 失败检查前台站点的 /api/ 反向代理、后端 9080 服务和浏览器控制台请求地址。

16. 回滚

操作位置:Mac 本地终端。

部署异常时,使用 Git 回滚后再次推送即可触发相应工作流:

git log --oneline
git revert 提交编号
git push origin main

不要在服务器发布目录中手工混放文件:前台、后台工作流使用 rsync --delete,会清理构建产物中不存在的旧文件。

17. 官方资料