第 40 章:部署与运维 —— 上线 SmartTodo
三十九章。从 AI 编程时代的到来,到 Agent 与模型的核心概念,到环境搭建、日常操作、进阶掌握、生态扩展、八个实战场景、方法论的建立、高级工作流、自定义扩展、团队推广、模型对比,再到贯穿项目的 SmartTodo 从零搭建——我们走过了整整三十九章的旅程。
现在,只差最后一步:让 SmartTodo 真正上线,让全世界能访问它。
部署与运维,在传统软件开发中往往是独立团队负责的领域——DevOps 工程师写 Dockerfile、配 CI/CD、管服务器、看监控。但在 AI 辅助开发的时代,Claude Code 可以帮助你完成所有这些工作,即使你之前从未独自部署过一个完整应用。
本章是贯穿项目的终点,也是全书的收官之战。我们将把第 37-39 章构建的 SmartTodo 应用,从本地开发环境推送到生产环境。同时,在最后一节,我们将回顾整个 40 章的旅程,提炼出最重要的原则和下一步的方向。
本章目标:完成 SmartTodo 的生产环境部署全流程——从构建配置、CI/CD 流水线、平台部署到监控日志。最终,以全书总结为这段学习旅程画上圆满句号。
40.1 构建配置
部署的第一步是为项目创建生产环境的构建配置。我们之前一直在开发环境运行——pnpm dev 启动 Vite 开发服务器,Node.js 后端用 nodemon 热重载。生产环境需要完全不同的方式:容器化、反向代理、环境变量管理、构建产物优化。
40.1.1 Docker 化后端
进入后端项目目录,对 Claude Code 说:
"帮我配置生产环境构建。先为后端创建 Dockerfile,使用多阶段构建优化镜像大小。"
Claude Code 会分析项目的 package.json 和技术栈(Node.js + Express + PostgreSQL),生成一个生产级的 Dockerfile:
# backend/Dockerfile
# ---- Stage 1: Build ----
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --frozen-lockfile --prod=false
COPY tsconfig.json ./
COPY src/ ./src/
COPY prisma/ ./prisma/
RUN pnpm prisma generate
RUN pnpm build
# ---- Stage 2: Production ----
FROM node:20-alpine AS runner
RUN addgroup --system --gid 1001 app && \
adduser --system --uid 1001 app
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules/.pnpm ./node_modules/.pnpm
COPY --from=builder /app/node_modules/.modules.yaml ./node_modules/
COPY --from=builder /app/package.json ./
COPY --from=builder /app/prisma ./prisma/
# 只安装生产依赖
RUN npm install -g pnpm && pnpm install --frozen-lockfile --prod
USER app
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
CMD ["node", "dist/index.js"]Claude Code 会解释多阶段构建的优势:Build 阶段包含 TypeScript 编译器和 devDependencies,生产阶段只保留运行时需要的文件,最终镜像体积从可能超过 1GB 缩小到约 150MB。
关于 Alpine:Alpine Linux 是 Docker 容器中最常用的轻量级基础镜像(约 5MB),但它使用 musl libc 而非 glibc。某些原生模块(如
bcrypt)在 Alpine 上可能遇到编译问题。如果你的项目有这类依赖,可以改用node:20-slim(基于 Debian,约 60MB)。Claude Code 会根据你的依赖自动判断。
40.1.2 Docker 化前端
"现在为前端创建 Dockerfile,也是多阶段构建——第一阶段 npm build,第二阶段用 nginx。"
# frontend/Dockerfile
# ---- Stage 1: Build ----
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
# ---- Stage 2: Serve with Nginx ----
FROM nginx:1.27-alpine AS runner
# 复制构建产物
COPY --from=builder /app/dist /usr/share/nginx/html
# 复制 nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD wget -q --spider http://localhost:80/ || exit 1
CMD ["nginx", "-g", "daemon off;"]40.1.3 Nginx 反向代理配置
"配置 nginx 作为前端静态文件服务器和 API 反向代理。"
这是生产环境的关键配置:Nginx 既要提供前端静态资源,又要将 /api 请求代理到后端服务。
# frontend/nginx.conf
server {
listen 80;
server_name _;
# Gzip 压缩
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_min_length 512;
gzip_vary on;
# 安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 静态资源(带缓存)
location /assets/ {
root /usr/share/nginx/html;
expires 1y;
add_header Cache-Control "public, immutable";
}
# 前端页面(SPA 路由)
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
# API 反向代理
location /api {
proxy_pass http://backend:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
# 超时设置
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# 健康检查端点(不记录日志)
location /health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
}
}Nginx 配置中的关键点:
| 配置项 | 作用 |
|---|---|
gzip | 压缩静态资源,减少传输量(典型压缩比 70%) |
try_files $uri /index.html | SPA 路由支持——非文件路径回退到 index.html |
proxy_pass http://backend:3000 | API 请求转发到 Docker Compose 中的 backend 服务 |
proxy_set_header | 转发真实客户端 IP 和协议信息 |
| 安全头 | 防御常见 Web 攻击(点击劫持、MIME 嗅探、XSS) |
40.1.4 Docker Compose 编排
"创建 docker-compose.yml,把前后端和数据库编排在一起,配置生产环境变量。"
# docker-compose.yml
services:
postgres:
image: postgres:16-alpine
container_name: smarttodo-db
restart: unless-stopped
environment:
POSTGRES_USER: ${DB_USER:-smarttodo}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME:-smarttodo}
volumes:
- pgdata:/var/lib/postgresql/data
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
ports:
- '127.0.0.1:5432:5432'
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U ${DB_USER:-smarttodo}']
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
networks:
- app-network
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: smarttodo-api
restart: unless-stopped
environment:
NODE_ENV: production
PORT: 3000
DATABASE_URL: postgresql://${DB_USER:-smarttodo}:${DB_PASSWORD}@postgres:5432/${DB_NAME:-smarttodo}
JWT_SECRET: ${JWT_SECRET}
CORS_ORIGIN: ${CORS_ORIGIN:-https://your-domain.com}
LOG_LEVEL: ${LOG_LEVEL:-info}
SENTRY_DSN: ${SENTRY_DSN:-}
depends_on:
postgres:
condition: service_healthy
networks:
- app-network
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: smarttodo-web
restart: unless-stopped
ports:
- '${WEB_PORT:-8080}:80'
depends_on:
- backend
networks:
- app-network
# 可选:每日数据库备份
backup:
image: postgres:16-alpine
container_name: smarttodo-backup
restart: unless-stopped
environment:
PGPASSWORD: ${DB_PASSWORD}
volumes:
- ./backups:/backups
- ./scripts/backup.sh:/backup.sh:ro
entrypoint: ['/bin/sh', '-c']
command: |
while true; do
sleep 86400
pg_dump -h postgres -U ${DB_USER:-smarttodo} -d ${DB_NAME:-smarttodo} \
-f /backups/smarttodo_$$(date +%Y%m%d).sql
find /backups -name '*.sql' -mtime +7 -delete
done
depends_on:
- postgres
networks:
- app-network
volumes:
pgdata:
driver: local
networks:
app-network:
driver: bridgeDocker Compose 编排的关键设计决策:
restart: unless-stopped:容器在崩溃或宿主机重启后自动恢复,除非手动停止depends_on: condition: service_healthy:后端等待数据库真正就绪(而非仅容器启动),避免 "ECONNREFUSED" 竞态问题- 数据库端口绑定到
127.0.0.1:只允许本地连接,不直接暴露到公网 - backup 服务:每天自动 dump 数据库,保留最近 7 天的备份文件
- 敏感信息通过环境变量注入:生产密钥不在 docker-compose.yml 中硬编码
配套的备份脚本和数据库初始化:
-- db/init.sql(PostgreSQL 容器首次启动时自动执行)
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- 如果使用 Prisma,migrate 由后端启动脚本处理
-- 这里可以添加初始数据或扩展配置# scripts/backup.sh
#!/bin/sh
set -e
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="/backups/smarttodo_${DATE}.sql.gz"
pg_dump -h postgres -U "${DB_USER:-smarttodo}" -d "${DB_NAME:-smarttodo}" | gzip > "$BACKUP_FILE"
echo "Backup created: ${BACKUP_FILE}"
# 保留最近 7 天的备份
find /backups -name '*.sql.gz' -mtime +7 -delete
echo "Old backups cleaned up."40.1.5 环境变量管理
生产环境的配置管理与开发环境截然不同。你不能把密钥写在 .env 文件里然后提交到 Git——那样做等同于把家门钥匙放在门口地垫下。
推荐的生产环境变量管理层次:
开发环境:
.env(明文,不提交)
└─ 开发用的低权限密钥、localhost 地址
CI 环境:
GitHub Secrets / GitLab Variables
└─ 构建时注入,日志中自动屏蔽
生产环境:
平台 Secret 管理(Railway Variables / Render Environment / Docker Swarm Secrets)
└─ 加密存储,运行时注入,审计日志.env.example 文件(提交到 Git,作为配置模板):
# .env.example —— 配置模板(可安全提交到版本控制)
# 复制为 .env 后填入真实值
# 数据库
DB_USER=smarttodo
DB_PASSWORD=change-me-to-a-strong-random-string
DB_NAME=smarttodo
DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@localhost:5432/${DB_NAME}
# 认证
JWT_SECRET=generate-a-random-256-bit-secret
# 生成方式:openssl rand -base64 32
# 前端配置
VITE_API_BASE_URL=http://localhost:3000/api
# 生产环境额外配置
CORS_ORIGIN=https://your-domain.com
LOG_LEVEL=info
SENTRY_DSN=
WEB_PORT=8080安全提示:永远不要将
.env文件提交到 Git。在项目初始化时就应该将.env加入.gitignore。Claude Code 会帮你检查这一点——如果你问它"检查一下我的项目有没有安全问题",它会主动指出.env是否被意外提交。
40.1.6 构建优化
"帮我分析构建产物大小,优化打包。"
Claude Code 会分析前端的 Vite 构建输出:
dist/index.html 0.48 kB
dist/assets/index-d2e3f4a5.css 12.34 kB (gzip: 3.21 kB)
dist/assets/index-a1b2c3d4.js 245.67 kB (gzip: 78.45 kB)
dist/assets/vendor-x9y8z7w6.js 389.12 kB (gzip: 121.34 kB)然后给出优化建议并实施:
// vite.config.ts —— Claude Code 优化的配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [vue(), visualizer({ open: false, gzipSize: true })],
build: {
// 代码分割策略
rollupOptions: {
output: {
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'pinia'],
'ui-vendor': ['@your-ui-library'],
'utils': ['dayjs', 'lodash-es'],
},
},
},
// 资源内联阈值:小于 4KB 的 CSS 内联到 HTML
cssMinify: 'lightningcss',
// 启用 terser 压缩(比 esbuild 压缩率更高)
minify: 'terser',
terserOptions: {
compress: {
drop_console: true, // 移除 console.log
drop_debugger: true, // 移除 debugger 语句
},
},
// chunk 大小警告阈值
chunkSizeWarningLimit: 300,
},
// 生产环境移除 console 和 debugger(仅对生产构建生效)
esbuild: {
drop: process.env.NODE_ENV === 'production' ? ['console', 'debugger'] : [],
},
})后端方面,Claude Code 会检查并优化:
- 依赖瘦身:将 TypeScript、ESLint、Jest 等工具放到
devDependencies,生产镜像中不安装 - Prisma 查询优化:使用
select只取需要的字段,避免SELECT * - 连接池配置:根据预期并发量调整
connection_limit
优化效果对比:
| 指标 | 优化前 | 优化后 | 改善 |
|---|---|---|---|
| 前端 JS 总量(gzip) | 312 KB | 186 KB | -40% |
| 前端首次加载时间(3G) | 3.2s | 1.8s | -44% |
| 后端镜像大小 | 420 MB | 148 MB | -65% |
| 后端冷启动时间 | 12s | 4s | -67% |
40.2 CI/CD 流水线
有了生产级的构建配置,下一步是建立自动化的 CI/CD 流水线——让每次代码推送都自动经历 lint、测试、构建的检查链,确保只有质量达标的代码才能进入生产环境。
40.2.1 GitHub Actions 基础工作流
"创建 GitHub Actions 工作流,自动构建和测试。"
Claude Code 会创建一个完整的 CI 配置文件:
# .github/workflows/ci.yml
name: CI - Test & Build
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
# ========== Job 1: Lint 检查 ==========
lint:
name: Lint
runs-on: ubuntu-latest
strategy:
matrix:
project: [backend, frontend]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
cache-dependency-path: ${{ matrix.project }}/pnpm-lock.yaml
- name: Install dependencies
working-directory: ${{ matrix.project }}
run: pnpm install --frozen-lockfile
- name: Run ESLint
working-directory: ${{ matrix.project }}
run: pnpm lint
- name: Run Type Check (backend only)
if: matrix.project == 'backend'
working-directory: ${{ matrix.project }}
run: pnpm typecheck
# ========== Job 2: 测试 ==========
test:
name: Test
runs-on: ubuntu-latest
needs: lint
strategy:
matrix:
project: [backend, frontend]
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: smarttodo_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
cache-dependency-dependency-path: ${{ matrix.project }}/pnpm-lock.yaml
- name: Install dependencies
working-directory: ${{ matrix.project }}
run: pnpm install --frozen-lockfile
- name: Generate Prisma Client (backend)
if: matrix.project == 'backend'
working-directory: backend
run: |
echo "DATABASE_URL=postgresql://test:test@localhost:5432/smarttodo_test" >> .env.test
pnpm prisma generate
pnpm prisma migrate deploy
env:
DATABASE_URL: postgresql://test:test@localhost:5432/smarttodo_test
- name: Run tests
working-directory: ${{ matrix.project }}
run: pnpm test -- --coverage
- name: Upload coverage
uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-${{ matrix.project }}
path: ${{ matrix.project }}/coverage/
# ========== Job 3: 构建检查 ==========
build:
name: Build Check
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- name: Build backend Docker image
working-directory: backend
run: docker build -t smarttodo-api:ci .
- name: Build frontend Docker image
working-directory: frontend
run: docker build -t smarttodo-web:ci .这个 CI 流水线有三层防护:
- Lint(快速失败):约 1 分钟完成,发现代码风格和类型错误就立即终止
- Test(并行执行):前后端测试并行跑,约 3-5 分钟。后端测试使用 GitHub Actions 的 Service Container 启动真实 PostgreSQL
- Build(最终验证):确保 Docker 镜像能成功构建,约 2 分钟
总耗时约 6-8 分钟,对于一个中小型项目的 CI 来说是合理的。
40.2.2 Claude Code 自动 PR Review
"添加 Claude Code 自动 PR Review。"
在第 27 章我们详细讲解过 CI/CD 中 Claude Code 的集成方案。现在把它应用到 SmartTodo 项目:
# .github/workflows/claude-review.yml
name: Claude Code PR Review
on:
pull_request:
types: [opened, synchronize, reopened]
branches: [main]
jobs:
review:
name: AI Review
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Claude Code PR Review
uses: anthropics/claude-code-action@v1
with:
model: claude-sonnet-4-20250514
max-tokens: 4000
prompt: |
请审查这个 PR 的代码变更,重点关注:
1. **潜在 Bug**:空值处理、异步错误、边界条件、竞态条件
2. **安全问题**:硬编码密钥、SQL 注入、XSS、不安全的依赖
3. **代码质量**:命名清晰度、函数复杂度、重复代码、错误处理
4. **测试覆盖**:新增代码是否有对应测试
5. **SmartTodo 特定关注点**:
- 数据库查询是否有 N+1 问题
- API 端点是否正确处理了认证和授权
- 前端组件是否正确处理了加载和错误状态
请在评论中使用中文,对每个问题提供具体文件和行号,并给出修复建议。
如果代码质量很好,也请给出肯定。
continue-on-error: true # 非阻塞:AI Review 失败不阻止合并核心理念回顾:Claude Code 在 CI 中是辅助者,不是决策者。它的 Review 评论是参考性质的——供 Reviewer 参考,帮人类发现容易被忽略的问题。Merge 的决定权始终在人手里。
40.2.3 自动化部署流水线
"配置自动化测试,在部署前运行。另外,当代码合并到 main 分支时,自动触发部署。"
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
paths-ignore:
- '**.md'
- '.github/**'
- 'docs/**'
jobs:
# Job 1: 完整的 CI 检查(复用 ci.yml 可以抽成 reusable workflow)
ci:
uses: ./.github/workflows/ci.yml
# Job 2: 部署
deploy:
name: Deploy
runs-on: ubuntu-latest
needs: ci
if: success()
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push backend
uses: docker/build-push-action@v5
with:
context: ./backend
push: true
tags: ${{ secrets.DOCKER_REGISTRY }}/smarttodo-api:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build and push frontend
uses: docker/build-push-action@v5
with:
context: ./frontend
push: true
tags: ${{ secrets.DOCKER_REGISTRY }}/smarttodo-web:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Deploy to server
run: |
# 通过 SSH 在目标服务器上拉取新镜像并重启服务
ssh ${{ secrets.DEPLOY_HOST }} << 'EOF'
cd /opt/smarttodo
docker compose pull
docker compose up -d --remove-orphans
# 等待服务健康检查通过
sleep 15
docker compose ps
# 运行数据库迁移
docker compose exec -T backend npx prisma migrate deploy
# 清理旧镜像
docker image prune -af --filter "until=24h"
EOF
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Smoke test
run: |
sleep 5
curl -f https://api.your-domain.com/health || exit 1
curl -f https://your-domain.com/health || exit 1
echo "✅ Deployment verified successfully"
- name: Notify on failure
if: failure()
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "❌ SmartTodo 部署失败!\nCommit: ${{ github.sha }}\n查看: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}部署流水线的安全保障机制:
paths-ignore:文档和 CI 配置文件的修改不触发部署,避免无意义的重新部署needs: ci:必须先通过全部 CI 检查才能部署,Gate 机制- Smoke test:部署后立即运行冒烟测试,自动验证部署是否成功
- 失败通知:部署失败时自动发送 Slack/企业微信通知,团队成员第一时间知道
docker image prune:定期清理旧镜像,防止磁盘空间耗尽
40.3 部署上线
构建配置和 CI/CD 就绪后,终于到了最激动人心的时刻——把 SmartTodo 部署到真实的公网服务器上。
40.3.1 选择部署平台
"帮我部署到 Railway。"
Claude Code 会先询问你的部署偏好,然后根据你的选择提供对应的部署方案。以下是三个主流平台的对比和 Claude Code 能帮你做的事情:
| 平台 | 特点 | 适合场景 | 成本 |
|---|---|---|---|
| Railway | 一键部署,内置 PostgreSQL,自动 HTTPS | 个人项目到中型应用 | $5-20/月起 |
| Render | 免费层,原生 Docker 支持,Blue/Green 部署 | 学习和小型应用 | 免费层起步 |
| VPS(如 DigitalOcean) | 完全控制,需要手动配置更多 | 需要精细控制的中大型应用 | $6-24/月起 |
| Fly.io | 边缘部署,全球多区域 | 对延迟敏感的应用 | 免费层起步 |
以 Railway 为例(最快速的方案),Claude Code 会引导你完成以下步骤:
Step 1:安装 Railway CLI 并登录
# macOS
brew install railway
# 登录
railway login
# 初始化项目
railway initStep 2:创建 PostgreSQL 数据库
# Railway 会自动创建并注入 DATABASE_URL 环境变量
railway add
# 选择 "PostgreSQL"
railway variables set \
JWT_SECRET="$(openssl rand -base64 32)" \
CORS_ORIGIN="https://your-app.railway.app" \
NODE_ENV="production"Step 3:部署
Railway 会自动检测你的 Dockerfile 并构建部署。对于包含多个服务的项目(前后端 + 数据库),Claude Code 会帮你创建 railway.json:
{
"services": {
"backend": {
"build": {
"dockerfilePath": "./backend/Dockerfile"
},
"port": 3000,
"healthcheckPath": "/health"
},
"frontend": {
"build": {
"dockerfilePath": "./frontend/Dockerfile"
},
"port": 80,
"routes": {
"api": "backend.railway.internal:3000"
}
}
}
}部署命令:
# 部署所有服务
railway up
# 查看部署状态
railway status
# 查看日志
railway logs40.3.2 数据库迁移
"帮我运行数据库迁移。"
部署到生产环境后,数据库迁移需要特别小心——你不能像在开发环境那样随意 prisma migrate dev(它会重置数据)。
# 生产环境的正确迁移方式
# Claude Code 会通过 Railway CLI 执行:
railway run --service backend -- npx prisma migrate deploy
# 验证迁移状态
railway run --service backend -- npx prisma migrate statusClaude Code 还会建议你在 CI/CD 部署步骤中自动运行迁移(参见 40.2.3 的 deploy job),并加上迁移失败的回滚策略:
# 迁移的安全运行方式
# 1. 先备份数据库
railway run --service backend -- \
npx prisma db pull --print > /tmp/pre_migration_schema.prisma
# 2. 运行迁移
railway run --service backend -- npx prisma migrate deploy
# 3. 如果迁移失败,查看状态
railway run --service backend -- npx prisma migrate status生产环境数据库迁移的第一原则:永远先在 staging 环境验证迁移,永远在迁移前备份数据,永远使用
migrate deploy(而非migrate dev)。
40.3.3 验证部署
"帮我验证部署是否成功。"
部署完成后,Claude Code 会运行一系列冒烟测试来验证:
# Claude Code 自动执行的部署验证脚本
echo "=== 1. 健康检查 ==="
curl -s https://api.your-domain.com/health
echo ""
echo "=== 2. API 可达性 ==="
curl -s https://api.your-domain.com/api/todos \
-H "Authorization: Bearer $TEST_TOKEN" | head -c 200
echo ""
echo "=== 3. 前端页面加载 ==="
curl -s -o /dev/null -w "HTTP Status: %{http_code}\n" https://your-domain.com/
echo "=== 4. 静态资源(gzip 压缩检查) ==="
curl -s -I -H "Accept-Encoding: gzip" \
https://your-domain.com/assets/index-*.js | grep -i content-encoding
echo "=== 5. CORS 配置检查 ==="
curl -s -I -X OPTIONS https://api.your-domain.com/api/todos \
-H "Origin: https://your-domain.com" | grep -i access-control
echo "=== 6. 数据库连接验证 ==="
curl -s https://api.your-domain.com/health/db
echo ""
echo ""
echo "✅ 所有检查通过"冒烟测试检查清单:
| 检查项 | 通过标准 | 失败意味着 |
|---|---|---|
/health | HTTP 200 | 服务未启动或崩溃 |
| API 响应 | HTTP 200 + JSON | 路由配置错误或认证问题 |
| 前端页面 | HTTP 200 | nginx 配置错误或构建产物缺失 |
| Gzip 压缩 | Content-Encoding: gzip | nginx gzip 未启用,带宽浪费 |
| CORS 头 | Access-Control-Allow-Origin | 跨域配置错误,前端无法调用 API |
| 数据库连接 | /health/db 返回 200 | 数据库连接失败或配置错误 |
如果某个检查失败,Claude Code 会帮你排查:
- 服务未响应 → 检查容器日志:
railway logs --service backend - 数据库连接失败 → 检查
DATABASE_URL环境变量和数据库状态 - 前端 404 → 检查 nginx 配置中的
try_files和 SPA 路由设置 - HTTPS 证书错误 → 确认平台是否已自动签发 SSL 证书(Railway/Render 通常自动完成)
40.4 监控与日志
部署上线不是终点——恰恰相反,上线是运维的起点。一个负责任的应用需要健康检查、日志记录、错误监控和数据备份。
40.4.1 健康检查端点
"设置基本的健康检查端点。"
健康检查是生产环境的基础保障。一个好的健康检查端点应该检查所有关键依赖,而不只是返回 200。
// backend/src/routes/health.ts
import { Router } from 'express'
import { PrismaClient } from '@prisma/client'
const router = Router()
const prisma = new PrismaClient()
// 基础健康检查 —— 进程存活
router.get('/health', (_req, res) => {
res.json({
status: 'ok',
service: 'smarttodo-api',
uptime: process.uptime(),
timestamp: new Date().toISOString(),
node_version: process.version,
})
})
// 深度健康检查 —— 依赖检查
router.get('/health/db', async (_req, res) => {
try {
await prisma.$queryRaw`SELECT 1`
res.json({
status: 'ok',
database: 'connected',
timestamp: new Date().toISOString(),
})
} catch (error) {
res.status(503).json({
status: 'error',
database: 'disconnected',
message: error instanceof Error ? error.message : 'Unknown error',
timestamp: new Date().toISOString(),
})
}
})
// 就绪检查 —— Kubernetes 风格
router.get('/health/ready', async (_req, res) => {
const checks: Record<string, boolean> = {}
// 数据库
try {
await prisma.$queryRaw`SELECT 1`
checks.database = true
} catch {
checks.database = false
}
// 可以添加更多检查:Redis、S3、外部 API 等
const allHealthy = Object.values(checks).every(Boolean)
res.status(allHealthy ? 200 : 503).json({
status: allHealthy ? 'ready' : 'not ready',
checks,
timestamp: new Date().toISOString(),
})
})
export default routerDocker Compose 和负载均衡器使用这些端点:
# docker-compose.yml 中的健康检查配置
healthcheck:
test: ['CMD', 'node', '-e',
"require('http').get('http://localhost:3000/health', (r) => {
process.exit(r.statusCode === 200 ? 0 : 1)
})"]
interval: 15s
timeout: 5s
retries: 3
start_period: 10s40.4.2 请求日志中间件
"添加请求日志中间件。"
生产环境的日志和开发环境不同——你需要结构化日志(JSON 格式),方便后续聚合分析。
// backend/src/middleware/logger.ts
import { Request, Response, NextFunction } from 'express'
interface LogEntry {
timestamp: string
level: 'info' | 'warn' | 'error'
method: string
path: string
status: number
duration_ms: number
ip: string
user_agent?: string
user_id?: string
error?: string
}
export function requestLogger(req: Request, res: Response, next: NextFunction) {
const start = Date.now()
// 响应完成时记录
res.on('finish', () => {
const entry: LogEntry = {
timestamp: new Date().toISOString(),
level: res.statusCode >= 500 ? 'error' : res.statusCode >= 400 ? 'warn' : 'info',
method: req.method,
path: req.originalUrl,
status: res.statusCode,
duration_ms: Date.now() - start,
ip: req.ip || req.socket.remoteAddress || 'unknown',
user_agent: req.get('User-Agent')?.slice(0, 100),
}
// 如果有认证用户,记录用户 ID
if ((req as any).user?.id) {
entry.user_id = (req as any).user.id
}
// 生产环境输出 JSON,开发环境输出美化格式
if (process.env.NODE_ENV === 'production') {
console.log(JSON.stringify(entry))
} else {
const color = res.statusCode >= 500 ? '\x1b[31m' : res.statusCode >= 400 ? '\x1b[33m' : '\x1b[32m'
console.log(
`${color}${req.method}\x1b[0m ${req.originalUrl} ` +
`${color}${res.statusCode}\x1b[0m ` +
`${entry.duration_ms}ms`
)
}
})
next()
}
// 错误日志中间件(放在所有路由之后)
export function errorLogger(
err: Error,
req: Request,
_res: Response,
next: NextFunction
) {
console.error(JSON.stringify({
timestamp: new Date().toISOString(),
level: 'error',
message: err.message,
stack: process.env.NODE_ENV === 'production' ? undefined : err.stack,
method: req.method,
path: req.originalUrl,
ip: req.ip || req.socket.remoteAddress,
}))
next(err)
}在 index.ts 中注册:
import express from 'express'
import { requestLogger, errorLogger } from './middleware/logger'
const app = express()
// 日志中间件要放在最前面
app.use(requestLogger)
// ... 其他中间件和路由 ...
// 错误日志中间件放在最后
app.use(errorLogger)40.4.3 错误监控
"配置错误监控。"
对于生产环境的错误监控,Sentry 是目前最主流的选择。它有完善的免费层(每月 5000 个事件),完美适合 SmartTodo 这种规模的项目。
# 安装 Sentry SDK
cd backend && pnpm add @sentry/node @sentry/profiling-node// backend/src/instrument.ts(在应用入口最开始导入)
import * as Sentry from '@sentry/node'
import { nodeProfilingIntegration } from '@sentry/profiling-node'
Sentry.init({
dsn: process.env.SENTRY_DSN,
// 性能监控:只采样 10% 的请求(控制成本)
tracesSampleRate: 0.1,
// Profiling:在采样的请求中进行性能剖析
profilesSampleRate: 0.1,
// 环境标识
environment: process.env.NODE_ENV || 'development',
// 不发送这些错误(减少噪音)
ignoreErrors: [
'ValidationError', // Prisma 验证错误
'UnauthorizedError', // JWT 认证错误
'NotFoundError', // 404
],
// 敏感数据脱敏
beforeSend(event) {
// 移除请求中的密码字段
if (event.request?.data) {
delete event.request.data.password
delete event.request.data.token
delete event.request.data.jwtSecret
}
return event
},
})// backend/src/index.ts
import './instrument' // 必须第一行导入
import express from 'express'
import * as Sentry from '@sentry/node'
const app = express()
// Sentry 请求处理器(在所有路由之前)
app.use(Sentry.Handlers.requestHandler())
app.use(Sentry.Handlers.tracingHandler())
// ... 你的路由 ...
// Sentry 错误处理器(在所有路由之后)
app.use(Sentry.Handlers.errorHandler())
// 通用错误处理
app.use((err: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
console.error('Unhandled error:', err.message)
res.status(500).json({
error: process.env.NODE_ENV === 'production'
? 'Internal Server Error'
: err.message,
})
})前端也可以接入 Sentry:
// frontend/src/main.ts
import * as Sentry from '@sentry/vue'
const app = createApp(App)
if (import.meta.env.PROD) {
Sentry.init({
app,
dsn: import.meta.env.VITE_SENTRY_DSN,
environment: 'production',
tracesSampleRate: 0.1,
})
}
app.mount('#app')40.4.4 状态监控页面
"创建简单的状态监控页面。"
一个轻量级的内部状态页面,无需外部服务:
// backend/src/routes/status.ts
import { Router } from 'express'
import * as os from 'os'
const router = Router()
// 获取系统状态(需要管理员权限访问)
router.get('/status', async (_req, res) => {
const memUsage = process.memoryUsage()
res.json({
status: 'ok',
server: {
hostname: os.hostname(),
platform: os.platform(),
uptime: process.uptime(),
node_version: process.version,
},
memory: {
rss_mb: Math.round(memUsage.rss / 1024 / 1024),
heap_used_mb: Math.round(memUsage.heapUsed / 1024 / 1024),
heap_total_mb: Math.round(memUsage.heapTotal / 1024 / 1024),
system_free_mb: Math.round(os.freemem() / 1024 / 1024),
system_total_mb: Math.round(os.totalmem() / 1024 / 1024),
},
cpu: {
load_avg: os.loadavg(),
cores: os.cpus().length,
},
process: {
pid: process.pid,
ppid: process.ppid,
},
timestamp: new Date().toISOString(),
})
})
export default router注意:
/status端点暴露了服务器的内部信息,在生产环境中应该添加认证保护(JWT 验证或 IP 白名单),只允许管理员访问。
40.4.5 数据库备份策略
备份是运维中最容易被忽视但也最关键的一环。Docker Compose 中已经包含了一个 backup 服务(见 40.1.4),这里补充完整的备份策略:
备份策略:3-2-1 原则
📁 本地备份(backup 容器)
├─ 每日全量备份:pg_dump → /backups/smarttodo_YYYYMMDD.sql.gz
└─ 保留策略:最近 7 天
☁️ 异地备份(可选,通过定时任务)
├─ 每周上传到 S3/Cloudflare R2
└─ 保留策略:最近 4 周
🔧 恢复演练
└─ 每月一次:在新数据库中恢复最新备份,验证可用性自动化异地备份的补充脚本:
# scripts/backup-to-s3.sh
#!/bin/sh
# 每周运行一次,将最新备份上传到 S3
LATEST_BACKUP=$(ls -t /backups/smarttodo_*.sql.gz | head -1)
if [ -z "$LATEST_BACKUP" ]; then
echo "No backup found!"
exit 1
fi
# 使用 AWS CLI 上传到 S3
aws s3 cp "$LATEST_BACKUP" \
"s3://${S3_BUCKET}/smarttodo/$(basename "$LATEST_BACKUP")" \
--storage-class STANDARD_IA
echo "Backup uploaded: $(basename "$LATEST_BACKUP")"
# 清理 S3 中超过 30 天的备份
aws s3 ls "s3://${S3_BUCKET}/smarttodo/" | while read -r line; do
createDate=$(echo "$line" | awk '{print $1}')
fileName=$(echo "$line" | awk '{print $4}')
createDateEpoch=$(date -d "$createDate" +%s)
thirtyDaysAgo=$(date -d '30 days ago' +%s)
if [ "$createDateEpoch" -lt "$thirtyDaysAgo" ]; then
aws s3 rm "s3://${S3_BUCKET}/smarttodo/$fileName"
echo "Deleted old backup from S3: $fileName"
fi
done40.5 全书总结:从入门到精通
SmartTodo 上线了。四篇贯穿项目(第 37-40 章)交付了一个完整的前后端应用,从项目初始化、后端 API 开发、前端页面构建,到生产环境部署的全流程。
但更大的图景是:从第 1 章到第 40 章,你走过的是一段怎样的旅程?
40.5.1 旅程回顾:你学了什么
四十章,八大阶段,一条成长曲线:
┌──────────────────────────────────────────────────────────────────┐
│ │
│ 第一篇:入门篇(第 1-4 章) │
│ ├─ 认识 AI 编程时代的到来 │
│ ├─ 理解 Agent 与模型的核心概念 │
│ ├─ 完成环境搭建,跑通第一个对话 │
│ └─ 了解平台生态 │
│ │
│ 第二篇:基础篇(第 5-9 章) │
│ ├─ 掌握四大核心能力:对话、编辑、终端、Git │
│ ├─ 学会 Diff 审查和代码回退 │
│ └─ 理解模式切换与权限控制 │
│ │
│ 第三篇:进阶篇(第 10-14 章) │
│ ├─ 理解 Agent Loop 内部运作机制 │
│ ├─ 精通上下文管理和 CLAUDE.md 编写 │
│ ├─ 掌握四层配置体系 │
│ └─ 建立权限与安全的最佳实践 │
│ │
│ 第四篇:生态篇(第 15-19 章) │
│ ├─ 掌握 MCP 协议与服务器扩展 │
│ ├─ 使用 Plugins、Skills、Hooks │
│ └─ 自定义命令与快捷键 │
│ │
│ 第五篇:实战篇(第 20-27 章) │
│ ├─ 八个真实开发场景完整演练 │
│ ├─ 从理解项目到 CI/CD 自动化 │
│ └─ 每个场景可复用的方法论 │
│ │
│ 第六篇:方法论篇(第 28-31 章) │
│ ├─ Prompt 工程的系统化方法 │
│ ├─ 设计 AI 友好型项目 │
│ ├─ 人机协作的分工原则 │
│ └─ 效率优化的经济学思维 │
│ │
│ 第七篇:精通篇(第 32-36 章) │
│ ├─ 高级工作流:多 Agent、Plan 模式、Worktree │
│ ├─ 自定义扩展开发 │
│ ├─ 团队推广的完整框架 │
│ ├─ 模型深度实测对比 │
│ └─ 前沿趋势与未来定位 │
│ │
│ 第八篇:贯穿项目(第 37-40 章) │
│ ├─ SmartTodo 从零到上线的完整实战 │
│ ├─ 前后端 + DevOps 全链路 │
│ └─ 所有知识点的融会贯通 │
│ │
└──────────────────────────────────────────────────────────────────┘这不仅仅是 40 章的篇幅堆砌——它是一条经过设计的成长曲线:
- 第 1-9 章:让你"能用"——在 Claude Code 的辅助下完成日常开发任务
- 第 10-19 章:让你"会用"——理解原理、会用生态、能自定义配置
- 第 20-31 章:让你"用好"——通过实战和方法论,形成高效的 AI 协作模式
- 第 32-40 章:让你"精通"——高级工作流、团队推广、完整项目交付
40.5.2 核心能力回顾
回看本书,你掌握的核心能力可以概括为八个维度:
┌──────────┐
│ 对话 │
└────┬─────┘
│
┌──────────────┼──────────────┐
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ 编辑 │ │ 终端 │ │ Git │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└──────────────┼──────────────┘
│
┌─────────┴─────────┐
│ │
┌────┴────┐ ┌────┴────┐
│ 配置 │ │ 生态 │
│ MCP │ │ Plugin │
│ Hook │ │ Skill │
└────┬────┘ └────┬────┘
│ │
└─────────┬─────────┘
│
┌─────────┴─────────┐
│ │
┌────┴────┐ ┌────┴────┐
│ 实战 │ │ 方法论 │
│ 8 场景 │ │ 人机协作│
└────┬────┘ └────┬────┘
│ │
└─────────┬─────────┘
│
┌────┴────┐
│ 精通 │
│ 团队推广 │
│ 完整项目 │
└─────────┘40.5.3 最重要的三条原则
如果整本书只能留下三句话,那就是:
原则一:Claude Code 是加速器,你是驾驶员
Claude Code 可以在一分钟内生成你半小时才能写完的代码,可以在几秒钟内扫描整个项目找出潜在问题,可以同时启动多个子代理并行完成任务。
但它不负责决策。
它不知道你的业务背景,不理解用户的情感诉求,无法权衡短期效率和长期可维护性之间的微妙平衡。它给出的代码需要你来判断:"这真的是我需要的吗?"它提出的方案需要你来审视:"这适合当前的项目架构吗?"
你的角色变化:
从 "写代码的人" → 变成 "定义目标、审查输出、做决策的人"
Claude Code 的角色:
从 "代码补全工具" → 变成 "执行层面的协作者"
这个变化不是削弱了你的价值,而是放大了它。
高效执行 + 正确决策 = 10x 产出原则二:先设计再执行,永远 Review 输出
这是全书反复强调的核心工作流:
❌ 错误方式:
"帮我写一个用户系统"
(然后全盘接受 Claude Code 生成的所有代码)
✅ 正确方式:
1. 先让我理清需求 —— "我需要支持邮箱注册和 OAuth 登录..."
2. 让 Claude Code 出方案 —— "先设计 API 路由和数据模型"
3. Review 方案 —— "这个设计符合我们的认证规范吗?"
4. 再编码 —— "按这个方案实现"
5. 逐块 Review Diff —— 每个文件都要看,不只是 Accept All
6. 运行测试验证 —— "帮我跑一下测试"这里面的每一步都不能省略。AI 能让你跑得更快,但如果你不看路,跑得越快摔得越惨。
原则三:工具在进化,你的角色也在进化
这本书写于 2025-2026 年。当你读到它时,Claude Code 的某些功能可能已经升级了,某些界面可能已经改变了,某些限制可能已经被解决了。
但有一件事不会变:作为开发者,你的核心竞争力不是"会用一个工具",而是"能用任何工具高效解决问题"。
保持学习的习惯。关注 Claude Code 的更新日志。尝试新的工作流和模型组合。把你在本书中学到的方法论——而不是具体的操作步骤——应用到不断进化的工具生态中。
五年前的开发者:熟练掌握某个 IDE 和框架就是竞争力。
现在的开发者:竞争力是"人机协作能力"——你指挥 AI 的效率、你审查 AI 输出的质量、你把复杂问题分解为 AI 可执行子任务的能力。
五年后的开发者:也许"写代码"这个动作本身不再是工作内容,而"定义问题、设计系统、验收质量"才是日常。你在本书中学到的人机协作能力,正是为那一天做准备。
40.5.4 下一步
全书到了终点,但你的 AI 编程之路才刚刚开始。以下是五个切实可行的下一步:
1. 把学到的应用到你的真实项目
找一个问题、一个项目、一段需要重构的旧代码——用 Claude Code 来处理它。书本知识只有付诸实践才能内化为能力。从一个小功能开始:用 Claude Code 给它写测试,用它来重构一个超过 100 行的函数,用它来给项目补充 CLAUDE.md。
2. 关注 Claude Code 的更新和新功能
AI 编程工具的发展速度以月为单位。订阅 Anthropic 的 Changelog,关注 Claude Code 的 Release Notes。当新功能(新的 Agent 能力、新的模型、新的集成方式)出现时,花 15 分钟了解一下——你永远不知道哪个新功能会彻底改变你的工作流。
3. 分享你的经验,帮助更多人
写博客、做内部分享、帮同事上手。教别人是最好的学习方式。当你把一个用 Claude Code 获得的技巧教给团队时,你自己的理解也会变得更深刻。而且,帮助他人提升效率所带来的成就感,远超独自精进。
4. 探索你的"第二曲线"
Claude Code 省下来的时间,你打算用来做什么?
- 学习一门你一直想学但"没时间"的编程语言
- 深入一个你感兴趣但"太复杂"的技术领域(系统编程、编译器、分布式系统)
- 开始写你那个"有空就做"的开源项目
- 更多的时间陪家人、锻炼身体、读书——这些同样重要
技术工具的意义不在于让你工作更久,而在于让你在更短的时间内完成工作,然后把省下来的时间投入到真正重要的事情上。
5. 保持批判性思维
AI 编程工具是强大的,但不是完美的。它会写出看起来正确但有微妙 Bug 的代码;它会在你不确定的领域给出看似自信的答案;它可能生成不符合最佳实践的代码模式。保持健康的怀疑态度:尊重它的能力,但不盲从它的输出。永远做最后的审查者和决策者。
40.5.5 致谢
本书的成形得益于许多人和事的支持,但最核心的是:
感谢 Anthropic 团队,他们创造的 Claude 模型和 Claude Code 工具,正在改变全球数百万开发者的工作方式。Agentic Coding 从概念到产品,Anthropic 做出了开创性的贡献。
感谢 DeepSeek 团队,他们用极高的性价比让更多开发者能负担得起高质量的 AI 编程辅助。cc-switch 项目的存在,让模型选择和成本优化变得简单。
感谢 Claude Code 的开源社区,无数开发者在 GitHub Issues、Discord、博客文章中分享的经验和踩坑记录,为本书提供了宝贵的实战素材。
感谢你,读者。知识只有在被使用时才有价值。你选择花几十个小时来系统学习 Claude Code——这个选择本身,说明了你对自我提升的认真态度。本书能成为你 AI 编程之路上的一块铺路石,是它存在的全部意义。
40.6 本章小结
最后一章的核心交付:
- 构建配置:多阶段 Docker 构建(Alpine 优化 + 安全非 root 用户),Nginx 反向代理(静态资源 + API 转发 + Gzip + 安全头),Docker Compose 服务编排(PostgreSQL + 前后端 + 自动备份)
- CI/CD 流水线:三层 GitHub Actions(Lint → Test → Build),Claude Code 自动 PR Review(非阻塞参考性质),main 分支自动部署 + 冒烟测试 + 失败通知
- 部署上线:Railway / Render / VPS 等多平台部署方案,数据库安全迁移策略(
migrate deploy+ 备份先行),部署后六项冒烟验证 - 监控与日志:多级健康检查(基础 / 深度 / 就绪),结构化 JSON 请求日志,Sentry 错误监控(前后端全链路),数据库 3-2-1 备份策略
SmartTodo 上线了
从第 37 章开始的那个"创建一个全栈待办应用"的想法,历经后端 API 开发、前端页面构建、到现在部署到公网——SmartTodo 正式上线了。你可以把它的 URL 发给朋友,他们能用真实的账号登录,创建待办事项,标记完成。
这是你亲手(和 Claude Code 一起)构建的。从 pnpm init 到 docker compose up -d,从第一行 TypeScript 到生产环境的健康检查端点。这 40 章赋予你的,不只是"会用 Claude Code",而是能用 Claude Code 独立完成一个完整产品的全部能力。
你毕业了
这是全书写下的最后一个章节。写下这几行字时,窗外是 2026 年初夏的阳光。我不知道你读到这段话时是何年何月、身处何地、为什么选择了这本书——但我确信一件事:你比以前更擅于和 AI 协作了。
四十章不是一个小数目。能走到这里,说明你有耐心、有行动力、有持续学习的品质。这三样东西,比任何具体的技术知识都重要——因为技术在变,但学习能力不变。
接下来该你了。去创建、去重构、去部署、去分享。把 Claude Code 用到你的真实工作中,用到你的 Side Project 里,用到你想解决的每一个问题上。
Enjoy coding — with your AI copilot by your side. 🎓