JY 项目一体化手册 Engineering Handbook
JY Engineering

开发、架构、部署与配置,一份手册讲清楚

面向维护者的单文件项目手册:从本地启动、共享 MySQL、静态预渲染,到管理端发布、 GitHub Actions 与 Docker/Nginx 生产落地。

仓库事实已核对 零外部依赖 可离线阅读 生产方案待落地
当前结论

核心发布闭环已经可用,但网站与管理端还不能称为生产完备。 jy-site 的静态生成、搜索、RSS 与主题链路基本打通;jy-admin 可完成登录、文章编辑、分类标签和发布触发。容器化、管理端自动部署、TLS、备份恢复、 回滚、完整 CI、细粒度权限与若干业务能力仍需补齐。

01项目总览

JY 是两个独立 Node 项目共处一个代码目录的博客系统。它们共享 MySQL blog_site,但承担完全不同的生产职责:

Public site jy-site

SvelteKit 5 在开发时实时查库,生产构建时把内容烘焙成纯静态文件,由 Nginx 托管。

Admin service jy-admin

Next.js 15 常驻 Node 服务,负责认证、内容写入、站点设置和触发公开站重新构建。

当前能力分级

内容链路 可用

已实现
写作、发布、预渲染与线上更新主路径存在。

公开站 基本完整

部分实现
阅读体验完整,404、sitemap、PWA 缓存等仍有缺口。

生产运维 未工程化

生产阻塞
容器、TLS、备份、健康检查和自动回滚均未入库。

当前实现:数据库驱动的静态发布链 实线 = 已实现链路
jy-admin Next.js · :5174 · 读写 MySQL blog_site 共享内容与站点设置 jy-site 构建 SvelteKit prerender 写入内容 构建时读取 GitHub Actions repository_dispatch Nginx 静态目录 build/ · rsync --delete 手动 / 自动触发 产出 build/
必须理解的静态边界

生产环境的 jy-site 没有 Node 服务,也不会实时查询 MySQL。数据库内容变更只有经过 build:static 和部署后才会上线;/api/search 仅用于开发态,生产搜索读取构建生成的 JSON。

02快速开始

前置环境

组件仓库约定说明
Node.js22.22.1根目录 .nvmrc 与 jy-admin engines 一致。
pnpm10.15.0两个项目各自维护 lockfile,没有根 workspace。
MySQLSchema 源自 5.7.44升级到 MySQL 8.x 前应在预发布环境完成导入与查询验证。

1. 初始化数据库

shell · 仓库根目录
mysql -u root -p < jy-site/blog_site.sql
mysql -u root -p blog_site < jy-site/migrations/001_site_deploy_settings.sql
mysql -u root -p blog_site < jy-site/migrations/002_auto_publish_setting.sql
初始化后仍不能直接登录

SQL dump 中的用户密码哈希是占位值,不是有效 bcrypt 凭证。首次启动前必须生成真实哈希并更新 users.password_hash;不要把明文密码写入 SQL 或 Git。

2. 配置两个应用

shell · 复制模板
cp jy-site/.env.example jy-site/.env
cp jy-admin/.env.example jy-admin/.env

# 编辑两个 .env:
# - DATABASE_* 指向同一个 blog_site
# - jy-admin 的 SESSION_SECRET 至少 32 字符
# - 需要发布按钮时再配置 GITHUB_REPOSITORY / GITHUB_TOKEN

3. 安装并启动

终端 A · 公开站

shell
cd jy-site
pnpm install
pnpm dev

默认 http://localhost:5173;端口占用时 Vite 会自动顺延。

终端 B · 管理端

shell
cd jy-admin
pnpm install
pnpm dev

固定 http://localhost:5174

常用质量命令

项目命令用途
jy-sitepnpm checkSvelteKit sync + svelte-check。
jy-sitepnpm lintPrettier 检查 + ESLint。
jy-sitepnpm build:static完整静态构建;不仅是 pnpm build
jy-adminpnpm checkTypeScript --noEmit
jy-adminpnpm verify类型检查 + Next.js 生产构建。
jy-adminpnpm dev:reset清理 .next 后重启,处理 HMR/Server Action 缓存异常。

03仓库与技术栈

“Monorepo” 的准确含义

这里是双应用同仓结构,但根目录没有 package.jsonpnpm-workspace.yaml。依赖安装、命令和 lockfile 都在两个子项目内独立管理。

目录地图
jy_prj/
├── .github/workflows/
│   ├── ci-jy-admin.yml            # 管理端 TypeScript CI
│   └── deploy-jy-site.yml         # 静态站构建与发布
├── jy-site/
│   ├── src/routes/                # SvelteKit 页面与端点
│   ├── src/lib/                   # 组件、Markdown、主题、数据库读取
│   ├── scripts/                   # 搜索索引、sitemap、构建规范化
│   ├── migrations/                # 手工 SQL 增量
│   ├── static/                    # PWA、图片、搜索索引等
│   ├── blog_site.sql              # 全量 schema + seed
│   └── nginx.conf                 # 当前 HTTP 静态站参考配置
├── jy-admin/
│   ├── app/                       # Next.js App Router
│   ├── components/                # Ant Design 管理组件
│   └── lib/                       # Server Actions、DB、会话、GitHub API
└── README.md                      # 当前高层说明

核心栈

技术职责
公开站Svelte 5 · SvelteKit 2 · Vite 7开发态 SSR 与生产静态预渲染。
管理端Next.js 15.5.14 · React 19.2.4 · Ant Design 5动态管理后台与发布控制面。
内容渲染markdown-it · ShikiMarkdown、VitePress 容器、双主题代码高亮。
数据层MySQL · mysql2/promise无 ORM;两端均使用手写 SQL。
认证iron-session · bcryptjs加密 Cookie 会话与密码校验。
发布GitHub Actions · rsync · Nginx当前静态站构建与同步。

工作区注意

本次核对时,当前目录未被 git status 识别为 Git worktree。GitHub Actions、 repository_dispatch 与推荐的自动化流程都以“代码已经位于真实 GitHub 仓库”为前提。

04公开站 jy-site

双运行模式

开发模式

  • Vite dev server 默认监听 0.0.0.0:5173
  • 页面 Server Load 实时读取 MySQL。
  • 搜索调用 GET /api/search?q=...
  • 更新数据库后刷新页面即可看到内容。

生产模式

  • adapter-static 输出到 jy-site/build/
  • 所有文章、分类、标签和 RSS 在构建时生成。
  • 搜索读取 /search-index.json
  • Nginx 只提供静态文件,不连接数据库。

路由清单

路由职责数据与预渲染
/首页与最新文章构建时读取最近文章。
/about建设初衷静态内容,参与预渲染。
/creed生活信条静态页面。
/life生命刻度静态页面 + 客户端计时。
/blog文章列表与分类胶囊文章、分类和分页设置。
/blog/page/[n]列表分页entries() 枚举可用页。
/blog/[slug]文章详情、目录、上下篇构建时 Markdown → HTML。
/blog/category/[slug]分类文章构建时枚举分类。
/blog/tag/[slug]标签文章构建时枚举标签。
/rss.xml最近 30 篇 RSS 2.0静态 XML。
/api/search开发态数据库搜索纯静态生产环境不可用,属于设计边界。

完整静态构建

  1. 生成搜索索引

    generate-search-index.ts 查询已发布文章并写入 static JSON。

  2. 清理并运行 SvelteKit build

    读取 MySQL,预渲染页面、数据依赖与 RSS。

  3. 生成 sitemap 与 robots

    优先使用数据库 site_url,其次使用 SITE_URL

  4. 规范化目录入口

    fix-build.tsroute.html 补成 route/index.html

唯一推荐的完整构建入口
cd jy-site
pnpm build:static
pnpm preview

功能状态

能力状态说明
Markdown 与代码高亮已实现markdown-it + Shiki,支持 tip/warning/danger/details。
分类与标签页面已实现详情页可跳转;标签首页导航与分类/标签分页未实现。
搜索部分实现开发 API + 生产 JSON;索引失败当前只警告并继续构建。
RSS已实现需要有效站点 URL 才能生成正确绝对链接。
Sitemap部分实现/creed/life、分页和 RSS。
PWA部分实现有 manifest/SW;固定 cache-first 可能长期返回旧页面。
404缺失没有自定义错误页,现有 Nginx 配置引用的 404.html 不存在。
评论、点赞、阅读数写入缺失字段或表存在,但公开站没有写入链路。
资源完整性检查

fonts.css 引用了多个 /fonts/* 文件,但当前 static/ 下未发现对应字体。浏览器会回退到系统字体,应在发布前补齐或移除无效引用。

05管理端 jy-admin

模块与路由

路由功能保护
/login用户名/密码登录已登录用户跳转文章列表。
/articles文章列表segment layout 会话守卫。
/articles/new新建文章与 Markdown 分屏同上。
/articles/[id]/edit编辑文章同上。
/categories分类 CRUD同上。
/tags标签 CRUD同上。
/settings站点与部署设置同上,但没有 admin-only RBAC。
/api/publish触发静态站构建API 内检查 session。
/api/publish/status查询最近 workflowAPI 内检查 session。

认证模型

  • 登录从 users 读取 bcrypt 哈希并校验 is_active
  • 会话由 iron-session 存放在加密的 jy_admin_session Cookie。
  • 生产 Cookie 使用 secure=true,因此管理端生产环境必须启用 HTTPS。
  • 会话有效期为 7 天,SESSION_SECRET 必须至少 32 字符。
  • 没有全局 middleware;四个业务 segment 各自通过 layout 守卫。
角色不是权限控制

系统只在登录时允许 adminauthoreditor, 登录后的 Server Actions 只检查“是否登录”。当前任意有效角色都能修改分类、标签、站点设置并触发部署。

数据写入结构

调用链
React Client Component
  └─ Server Action ('use server')
      ├─ getSession() / requireUser()
      ├─ getPool() → mysql2/promise
      ├─ 事务 / 参数化 SQL
      ├─ revalidatePath()
      └─ status=published 时可选 triggerSitePublish()

CRUD 覆盖

实体覆盖缺口
文章创建 / 读取 / 更新没有删除;可用 archived 代替软下线。
分类完整 CRUD被文章引用时禁止删除。
标签完整 CRUD删除时清理文章关联。
站点设置部分 KVenable_commentstheme 未暴露。
媒体仅 URL没有上传、对象存储或媒体库。
用户 / 评论未实现只有登录读取;没有用户管理和评论审核。

生产运行

shell
cd jy-admin
pnpm install --frozen-lockfile
pnpm verify
NODE_ENV=production pnpm start  # :5174

当前仓库没有 PM2、systemd、Dockerfile 或 jy-admin 部署 workflow。仅执行 pnpm start 不能提供进程自愈、TLS、日志轮转和发布回滚。

06数据库与数据流

blog_site 主要实体关系 author_id 在代码中使用,但数据库未建外键
articles content · status · slug · author_id category_id · published_at users 登录与作者资料 categories 1 : N · SET NULL site_settings 独立 KV 配置 article_tags M : N 关联表 tags slug · color · is_active comments Schema 有 · 应用未实现 无 FK

表职责

主要职责应用使用
articlesMarkdown、状态、分类、作者、计数与发布时间。admin 写;site 只读 published。
categories分类、slug、排序和启用状态。双端使用;site 当前未过滤 is_active。
tags标签、slug、颜色和启用状态。双端使用;site 当前未过滤 is_active。
article_tags文章与标签多对多关联。admin 事务替换;site 联表读取。
users管理登录与文章作者资料。admin 登录;site 显示作者。
site_settings站点与展示型部署配置。双端读取不同键。
comments评论与回复结构。未被应用代码使用。

Schema 管理现状

  • blog_site.sql 是 DROP/CREATE/INSERT 的全量 dump,不是可重复的日常 migration。
  • 001002 是手工 SQL,没有版本表和自动 runner。
  • 数据库中存在三个 VIEW,但应用继续使用手写 JOIN,没有调用它们。
  • articles.author_id 没有数据库外键,可能出现孤儿作者。
  • 没有备份、恢复、迁移校验或 schema drift 自动化。

07配置契约

应用环境变量

变量jy-sitejy-admin说明
DATABASE_HOST必需必需生产建议使用私网 DNS,例如 db
DATABASE_PORT可选可选默认 3306。
DATABASE_USER必需必需不要在生产使用 root。
DATABASE_PASSWORD按环境按环境只进入 secret store,不写入仓库。
DATABASE_NAME可选可选默认 blog_site
SITE_URL推荐sitemap、robots、RSS 的环境兜底。
SESSION_SECRET必需至少 32 字符,需长期稳定保存。
GITHUB_REPOSITORY发布功能owner/repo
GITHUB_TOKEN发布功能最小化授权,可 dispatch 且可读 Actions。
安全模板 · 不含真实值
DATABASE_HOST=127.0.0.1
DATABASE_PORT=3306
DATABASE_USER=jy_app
DATABASE_PASSWORD=${JY_DATABASE_PASSWORD}
DATABASE_NAME=blog_site
SITE_URL=https://blog.example.com

# jy-admin only
SESSION_SECRET=${JY_SESSION_SECRET}
GITHUB_REPOSITORY=example-org/jy-prj
GITHUB_TOKEN=${JY_GITHUB_TOKEN}

站点 URL 的优先级

公开站生成绝对链接时优先读取数据库 site_settings.site_url,其次读取 SITE_URL,最终会回退到占位域名。上线前必须至少配置前两者之一并保证一致。

GitHub Actions Secrets

当前 workflow 使用用途
DATABASE_*GitHub-hosted runner 在构建时直连数据库。
SITE_URL静态绝对 URL 兜底。
DEPLOY_SSH_HOSTrsync 目标主机。
DEPLOY_SSH_USERSSH 用户。
DEPLOY_SSH_KEY部署私钥。
DEPLOY_SSH_PATHNginx 静态目录。
立即处理示例凭证

两个 .env.example 当前包含看起来像真实数据库密码的示例值。应替换为明显占位符; 如果该值曾用于任何环境,先轮换凭证,再检查 Git 历史。本文档不会复制该值。

数据库部署设置不是控制面

管理端可保存 deploy_method、Nginx 路径和 SSH 展示字段,但当前 GitHub Actions 不读取这些键。实际部署分支和目标完全由 Actions Secrets 决定,因此两处配置可能漂移。

08内容发布

文章状态语义

状态管理端公开站
draft可保存和继续编辑。所有公开查询都不展示。
published首次进入状态时写入 published_at下一次构建后进入列表、详情、搜索和 RSS。
archived用于下线而不物理删除。下一次构建后不再生成或展示。

线上更新路径

  1. 编辑并保存文章

    jy-admin 在事务中写入 articles 与 article_tags。

  2. 决定是否触发构建

    published 且 auto_publish_on_save=1 时自动 dispatch;也可点击顶栏按钮。

  3. GitHub Actions 预渲染

    runner 连接 MySQL,生成页面、搜索索引、RSS、sitemap 和 robots。

  4. 同步静态产物

    当前使用 rsync --delete 到 Nginx 目录,或回退 gh-pages。

  5. 管理端轮询状态

    构建中每 5 秒、空闲时每 30 秒查询最近 workflow run。

连续保存行为

部署 workflow 设置 cancel-in-progress: true。短时间连续发布会取消前一轮构建, 只保留最新运行,适合单人博客,但构建与部署处于同一 job 时仍应避免在 rsync 阶段强制取消。

09当前部署链

已存在的自动化

  • deploy-jy-site.yml 支持手动触发和 jy-site-publish repository dispatch。
  • 工作流固定 Node 版本文件与 pnpm 10.15.0,执行完整 build-static.ts
  • 配置 SSH 主机时 rsync 到 Nginx;否则发布到 gh-pages 分支。
  • ci-jy-admin.yml 只运行 TypeScript 检查,不构建也不部署管理端。
当前工作流核心行为 · 摘要
pnpm install --frozen-lockfile
node --import tsx scripts/build-static.ts

rsync -avz --delete \
  jy-site/build/ "${DEPLOY_SSH_USER}@${DEPLOY_SSH_HOST}:${DEPLOY_SSH_PATH}/"

当前链路的生产风险

风险影响建议
runner 必须直连 MySQL可能迫使数据库暴露公网。改为自托管 runner、VPN/隧道,或推荐的服务器内构建。
Secrets 直接用于 step ifGitHub 对 secret 条件表达式支持受限,分支可能不按预期。使用非敏感 variable/input 选择部署目标。
rsync --delete 原地覆盖坏构建会立即替换线上且没有版本。版本目录 + 原子软链 + 保留最近发布。
搜索索引失败只警告可能部署旧索引或没有搜索。生产构建应 fail closed,并验证 JSON。
没有部署后 smoke testworkflow 成功不等于站点可用。检查首页、sitemap、搜索索引和代表文章。
Nginx 仅 HTTP管理端 secure Cookie 无法正常工作,流量未加密。部署 TLS 并强制 HTTP → HTTPS。

旧脚本不要作为生产入口

deploy.shdeploy-elegant.sh 和旧部署指南仍执行 pnpm build,并描述已经不存在的 /jysite base path 与缺失的 nginx-fix-403.conf。生产文档和脚本应统一到 pnpm build:static

10推荐 Docker 生产方案

建议方案,不是仓库现状

下面的 Dockerfile、Compose 与 Nginx 是可落地参考,但当前仓库中不存在这些文件。 实施时应单独评审、写入 deploy/ 并在预发布环境验证。

推荐拓扑:私网数据库 + 服务端静态构建 + 原子发布 虚线 = 触发;实线 = 运行时流量
Internet :443 blog / admin 子域名 Nginx TLS · 静态站 · 反向代理 jy-admin Next.js :5174 MySQL 仅 backend 网络 · 持久卷 site-builder 按需运行 · build:static GitHub Actions 仅 SSH 触发服务器脚本 site-releases/ 版本目录 + current 软链

建议目录

建议新增 · 尚未入库
deploy/
├── compose.production.yml
├── admin.Dockerfile
├── site-builder.Dockerfile
├── nginx.conf
├── deploy-site.sh
├── backup-db.sh
└── env/
    ├── admin.env.example
    ├── database.env.example
    └── site-build.env.example

/srv/jy/
├── app/                 # 真实 Git checkout
├── site-releases/
│   ├── 20260814-abcdef/
│   └── current -> 20260814-abcdef
└── backups/

Compose 参考

yaml · deploy/compose.production.yml(建议模板)
name: jy-production

services:
  db:
    image: mysql:8.4
    restart: unless-stopped
    env_file: ./env/database.env
    volumes:
      - mysql-data:/var/lib/mysql
      - ../jy-site/blog_site.sql:/docker-entrypoint-initdb.d/00-schema.sql:ro
      - ../jy-site/migrations/001_site_deploy_settings.sql:/docker-entrypoint-initdb.d/01-settings.sql:ro
      - ../jy-site/migrations/002_auto_publish_setting.sql:/docker-entrypoint-initdb.d/02-auto-publish.sql:ro
    healthcheck:
      test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -u root -p$$MYSQL_ROOT_PASSWORD --silent"]
      interval: 10s
      timeout: 5s
      retries: 12
    networks: [backend]

  admin:
    build:
      context: ../jy-admin
      dockerfile: ../deploy/admin.Dockerfile
    restart: unless-stopped
    env_file: ./env/admin.env
    environment:
      NODE_ENV: production
      DATABASE_HOST: db
      DATABASE_PORT: 3306
    depends_on:
      db:
        condition: service_healthy
    expose: ["5174"]
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:5174/login >/dev/null || exit 1"]
      interval: 20s
      timeout: 5s
      retries: 6
    networks: [frontend, backend]

  nginx:
    image: nginx:1.27-alpine
    restart: unless-stopped
    depends_on:
      admin:
        condition: service_healthy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - /srv/jy/site-releases:/srv/jy/site-releases:ro
      - /etc/letsencrypt:/etc/letsencrypt:ro
    networks: [frontend]

  site-builder:
    profiles: ["tools"]
    build:
      context: ..
      dockerfile: deploy/site-builder.Dockerfile
    env_file: ./env/site-build.env
    environment:
      DATABASE_HOST: db
      DATABASE_PORT: 3306
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - /srv/jy/site-releases:/releases
    networks: [backend]

volumes:
  mysql-data:

networks:
  frontend:
  backend:
    internal: true
MySQL 版本迁移要求

当前 dump 来自 MySQL 5.7.44。示例使用 8.4 LTS 作为目标,不代表已经兼容验证。 实施前必须在空库导入 schema、运行两端检查与静态构建,并验证字符集、时间戳、ENUM 和 SQL 行为。

管理端镜像参考

dockerfile · deploy/admin.Dockerfile(建议模板)
FROM node:22.22.1-alpine AS deps
RUN corepack enable && corepack prepare pnpm@10.15.0 --activate
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

FROM deps AS build
COPY . .
RUN pnpm build

FROM node:22.22.1-alpine AS runtime
RUN corepack enable && corepack prepare pnpm@10.15.0 --activate
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build --chown=node:node /app/package.json ./
COPY --from=build --chown=node:node /app/node_modules ./node_modules
COPY --from=build --chown=node:node /app/.next ./.next
COPY --from=build --chown=node:node /app/public ./public
USER node
EXPOSE 5174
CMD ["pnpm", "start"]

当前 Next 配置没有 output: 'standalone',因此参考镜像复制完整 node_modules。后续可单独启用 standalone 减小镜像,但必须重新验证 iron-session 与文件追踪。

静态构建镜像参考

dockerfile · deploy/site-builder.Dockerfile(建议模板)
FROM node:22.22.1-alpine
RUN corepack enable && corepack prepare pnpm@10.15.0 --activate
WORKDIR /workspace/jy-site
COPY jy-site/package.json jy-site/pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY jy-site/ ./
CMD ["pnpm", "build:static"]

Nginx 参考

nginx · deploy/nginx.conf(节选建议模板)
server {
  listen 80;
  server_name blog.example.com admin.example.com;
  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl;
  http2 on;
  server_name blog.example.com;
  ssl_certificate /etc/letsencrypt/live/blog.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/blog.example.com/privkey.pem;
  add_header Strict-Transport-Security "max-age=31536000" always;
  add_header X-Content-Type-Options "nosniff" always;

  root /srv/jy/site-releases/current;
  index index.html;

  location / {
    try_files $uri $uri.html $uri/ =404;
  }
  location ^~ /_app/immutable/ {
    expires 1y;
    add_header Cache-Control "public, immutable";
  }
  location = /search-index.json {
    add_header Cache-Control "public, max-age=300";
  }
  location = /healthz {
    access_log off;
    return 200 "ok\n";
  }
}

server {
  listen 443 ssl;
  http2 on;
  server_name admin.example.com;
  ssl_certificate /etc/letsencrypt/live/admin.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/admin.example.com/privkey.pem;
  add_header Strict-Transport-Security "max-age=31536000" always;
  add_header X-Content-Type-Options "nosniff" always;

  # 推荐在此加 VPN、SSO 或 allow/deny IP 策略
  location / {
    proxy_pass http://admin:5174;
    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;
  }
}

11自动化与回滚

推荐发布原则

  • GitHub Actions 只负责鉴权和触发,MySQL 不对 GitHub-hosted runner 开放。
  • 构建在服务器内部通过一次性 site-builder 容器完成。
  • 产物写入版本目录,验证成功后原子切换相对软链 current
  • 任何一步失败都不触碰当前线上版本;保留最近 5 个版本供秒级回滚。

服务器发布脚本参考

shell · deploy/deploy-site.sh(建议模板)
#!/usr/bin/env sh
set -eu

release_id="${1:?release id required}"
release_dir="/srv/jy/site-releases/${release_id}"

mkdir -p "${release_dir}"
docker compose -f deploy/compose.production.yml run --rm \
  --entrypoint sh site-builder -c \
  'pnpm build:static && test -s build/index.html && test -s build/sitemap.xml && test -s build/search-index.json && cp -a build/. "/releases/'"${release_id}"'/"'

ln -sfn "${release_id}" /srv/jy/site-releases/.current.next
mv -Tf /srv/jy/site-releases/.current.next /srv/jy/site-releases/current
docker compose -f deploy/compose.production.yml exec -T nginx nginx -s reload

curl --fail --silent --show-error https://blog.example.com/ >/dev/null
curl --fail --silent --show-error https://blog.example.com/sitemap.xml >/dev/null
脚本落地时要修正路径边界

上例展示发布语义。实际容器内挂载点与宿主机路径必须统一;旧版本清理由独立、受测试的保留策略完成, 并严格限制在 /srv/jy/site-releases。上线前先用临时目录演练。

Actions 触发参考

yaml · 关键步骤(建议模板)
- name: Configure SSH
  env:
    SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
    KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
  run: |
    install -m 700 -d ~/.ssh
    printf '%s\n' "$SSH_KEY" > ~/.ssh/deploy_key
    chmod 600 ~/.ssh/deploy_key
    printf '%s\n' "$KNOWN_HOSTS" > ~/.ssh/known_hosts

- name: Build and atomically publish on server
  env:
    HOST: ${{ secrets.DEPLOY_SSH_HOST }}
    USER: ${{ secrets.DEPLOY_SSH_USER }}
    RELEASE_ID: ${{ github.run_id }}-${{ github.run_attempt }}
  run: |
    ssh -i ~/.ssh/deploy_key "$USER@$HOST" \
      "cd /srv/jy/app && ./deploy/deploy-site.sh '$RELEASE_ID'"

不要使用运行时 ssh-keyscan 自动信任未知主机;把经过人工核验的 host key 存入 DEPLOY_KNOWN_HOSTS

回滚

shell · 原子切换到指定旧版本
cd /srv/jy/site-releases
test -s "20260813-previous/index.html"
ln -sfn "20260813-previous" .current.next
mv -Tf .current.next current
docker compose -f /srv/jy/app/deploy/compose.production.yml exec -T nginx nginx -s reload
curl --fail https://blog.example.com/

数据库备份与恢复演练

shell · 每日备份参考
umask 077
stamp="$(date +%Y%m%d-%H%M%S)"
mkdir -p /srv/jy/backups

docker compose -f deploy/compose.production.yml exec -T db sh -c \
  'exec mysqldump --single-transaction --routines --triggers \
    -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"' \
  | gzip -9 > "/srv/jy/backups/blog_site-${stamp}.sql.gz"

sha256sum "/srv/jy/backups/blog_site-${stamp}.sql.gz" \
  > "/srv/jy/backups/blog_site-${stamp}.sha256"
  • 备份文件应复制到异机或对象存储,不能只放在同一块磁盘。
  • 至少每季度在隔离数据库完成一次恢复演练并记录耗时。
  • 迁移前、批量内容修改前和账号变更前创建额外备份。

12安全与可观测性

生产安全基线

  • 数据库仅加入内部 Docker 网络,不映射宿主机 3306。
  • 管理端使用独立子域名和 HTTPS,并优先置于 VPN、零信任访问或 IP 白名单后。
  • 使用独立最小权限数据库用户;构建只读、管理端读写、备份单独授权。
  • SESSION_SECRET、数据库密码、GitHub PAT 和 SSH 私钥全部由 secret store 注入。
  • 对登录 API 加速率限制、失败审计和告警;高风险操作按角色授权。
  • Markdown 原始 HTML 只允许可信作者;当前公开渲染开启 html: true
  • 为管理端和 Nginx 设置安全响应头,并关闭不必要的版本暴露。

建议的最小 RBAC

角色建议权限
admin全部内容、用户、设置、部署与密钥状态。
editor编辑和发布所有文章、管理分类标签;不能改系统设置或用户。
author创建并编辑自己的草稿;发布需 editor/admin 审核。

健康检查与监控

检查当前建议目标
公开站 HTTP无专用端点Nginx /healthz + 首页与代表文章 synthetic check。
管理端就绪无专用端点新增只检查进程的 liveness 与检查 DB 的 readiness。
数据库连接时才暴露错误Compose healthcheck、连接池错误率与备份新鲜度告警。
部署只显示 Actions 结论构建产物校验、部署后 smoke test、失败自动回滚。
日志console + Nginx 默认日志JSON 结构化日志、request ID、轮转与集中保留。

13完备度审计

最终回答:现在还不完备

它适合本地使用、可信维护者的小型博客以及受控的静态站发布;在补齐下列 P0/P1 项之前,不应按“无人值守、可恢复、可审计的生产系统”来承诺。

P0

上线阻塞:先解决安全与可恢复性

  • 替换并轮换示例环境文件中的凭证式值,确认未进入 Git 历史。
  • 建立可用管理员账号生成流程,禁止继续使用占位 hash。
  • 启用 HTTPS,限制管理端访问,并把 MySQL 放入私网。
  • 建立异机数据库备份与恢复演练。
  • 修复 Actions 部署目标分支逻辑,取消数据库公网直连依赖。
  • 确认代码位于真实 Git 仓库并配置受保护默认分支。
P1

生产工程化:让部署可验证、可回滚

  • 落地 Docker/Compose、管理端部署、健康检查和结构化日志。
  • 静态站使用版本目录原子发布,部署后 smoke test,保留最近版本。
  • CI 覆盖 site check/lint/build 与 admin check/build;测试脚本纳入自动化。
  • 搜索索引失败改为构建失败;补齐 404、sitemap 与 PWA 更新策略。
  • 实现 RBAC、登录限流、失败审计;为高风险设置和发布操作加授权。
  • 补齐或移除缺失字体,清理旧部署脚本和过期路径文档。
P2

产品完整性:按真实需求扩展

  • 文章删除/回收站、媒体上传与对象存储。
  • 用户管理、评论审核、浏览/点赞统计。
  • 服务端文章搜索/筛选与大数据量分页。
  • 删除死依赖、未使用 DB VIEW 与无消费者配置,统一默认值。

上线前验收清单

  • 两个项目的 check/build 在干净环境通过。
  • MySQL 迁移、管理员登录、文章草稿/发布/归档均已演练。
  • 首页、文章、分类、标签、搜索、RSS、sitemap 与 404 已验证。
  • 管理端 HTTPS、Cookie、退出登录、RBAC 和限流已验证。
  • 从发布触发到静态上线的整条链路有日志和失败告警。
  • 静态版本回滚与数据库恢复各演练至少一次。
  • 域名、证书续期、备份保留、监控联系人有明确负责人。

14故障排查

现象优先检查处理
管理端启动即报 SESSION_SECRETjy-admin/.env生成并持久保存至少 32 字符 secret,重启进程。
数据库导入后无法登录users.password_hashseed 是占位 hash;生成 bcrypt hash 并更新用户。
文章已发布但线上没有Actions 最近 run、文章状态、DB 连通确认发生了完整 build:static + deploy,而非只保存数据库。
生产搜索为空build/search-index.json检查索引生成日志、文件 total 与缓存头;重新完整构建。
RSS/sitemap 出现占位域名site_settings.site_urlSITE_URL配置真实 HTTPS origin 后重新构建。
页面字体与设计不一致Network 中 /fonts/* 404补齐授权字体文件或删除对应 @font-face。
Next 开发热更新后 500.next 缓存运行 pnpm dev:reset
未知 URL 返回首页而非 404adapter fallback 与 Nginx try_files生产 Nginx 使用目录入口并以 =404 收尾。
SSH 部署分支未执行workflow 的 secret 条件改用 repository variable/input 选择 deploy target。
部署后静态资源混用旧版SW、浏览器缓存、原地 rsync更新 SW 策略并采用原子版本目录,必要时注销旧 SW。

诊断顺序

  1. 先确认是哪一层:浏览器、Nginx、admin Node、构建容器还是 MySQL。
  2. 记录请求 URL、时间、发布 run ID 与对应代码版本。
  3. 只执行不会破坏现场的只读检查;备份后再进行迁移或回滚。
  4. 修复后从用户路径复测,而不是只看进程退出码。

15事实来源

本手册把“仓库已有事实”和“建议生产方案”分开描述。以下文件是核对当前行为的主要来源; 相对链接在以文件方式打开手册时可直接定位到同一工作区文件。

主题源文件
整体架构README.md
公开站依赖与命令jy-site/package.json
静态适配器jy-site/svelte.config.js
完整静态构建jy-site/scripts/build-static.ts
搜索索引jy-site/scripts/generate-search-index.ts
公开站数据库连接jy-site/src/lib/server/mysql-pool.ts
管理端依赖与命令jy-admin/package.json
认证会话jy-admin/lib/session.ts
文章 Server Actionsjy-admin/lib/actions/article.ts
GitHub 发布集成jy-admin/lib/github.ts
数据库 Schemajy-site/blog_site.sql
当前发布 workflow.github/workflows/deploy-jy-site.yml
当前 Nginx 样例jy-site/nginx.conf
维护规则

当路由、环境变量、schema、构建入口或 workflow 发生变化时,同一 PR 应同步更新本手册。 建议每次生产发布前把“上线前验收清单”作为可执行 checklist 复核。

JY 项目一体化手册 · 工作区事实快照:2026-08-14 · 推荐配置尚未自动写入项目。

↑ ↓ 选择 · Enter 打开 · Esc 关闭