🌐 语言: English · Tiếng Việt · 中文 · 한국어
多阶段 Dockerfile(位置在项目根目录 Dockerfile):
# 阶段 1:构建
FROM node:20-alpine AS builder
WORKDIR /build
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install
COPY . .
RUN pnpm --filter @mobile-boilerplate/api build
# 阶段 2:运行时
FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /build/apps/api/dist ./dist
COPY --from=builder /build/apps/api/node_modules ./node_modules
COPY --from=builder /build/apps/api/package.json ./
EXPOSE 3000
CMD ["node", "dist/main.js"]本地构建:
docker build -t mobile-boilerplate-api:latest .在 CI 中构建(GitHub Actions):
- 触发于推送到
main或beta分支(在 api-ci 通过后) - 构建 docker 镜像
- 用 git sha + 分支标签标记
- 推送到 registry(例如 Docker Hub、ECR、GCR)
设置 CI 密钥:
DOCKER_REGISTRY—docker.io或私有 registry URLDOCKER_USERNAME— registry 用户名DOCKER_PASSWORD— registry 密码或令牌
生产 (.env):
DATABASE_URL="postgresql://user:pass@prod.supabase.co:5432/boilerplate"
DIRECT_URL="postgresql://user:pass@prod.supabase.co:5432/boilerplate"
SKIP_DB=false
NODE_ENV=production
LOG_LEVEL=info
API_PORT=3000
THROTTLE_LIMIT=100
THROTTLE_TTL=60000
JWT_SECRET="<use-secrets-manager>"暂存 (.env):
DATABASE_URL="postgresql://user:pass@staging.supabase.co:5432/boilerplate"
DIRECT_URL="postgresql://user:pass@staging.supabase.co:5432/boilerplate"
SKIP_DB=false
NODE_ENV=staging
LOG_LEVEL=debug
API_PORT=3000
JWT_SECRET="<use-secrets-manager>"生产部署选项:
| 平台 | 方法 | 说明 |
|---|---|---|
| Docker | docker run -e DATABASE_URL=... mobile-boilerplate-api |
简单、可移植 |
| Kubernetes | 使用 Helm chart 或清单 | 自动扩展、HA |
| Cloud Run (GCP) | 推送镜像、设置环境变量 | 无服务器、自动扩展 |
| ECS (AWS) | 任务定义 + 服务 | 托管容器 |
| Railway | 连接 GitHub、自动部署 | 小项目最简单 |
密钥管理:
- 从不提交
.env文件 - 使用平台特定密钥(GitHub 密钥、AWS 密钥管理、Vault)
- 在运行时通过环境变量或卷挂载注入
- 定期轮换密钥
自动化架构迁移(Prisma):
# 生成最新迁移文件
pnpm --filter @mobile-boilerplate/api prisma:migrate deploy在 CI/CD 中:
- 在启动服务器前运行(通过 Prisma 零停机时间)
- 回滚:git revert commit,重新部署(Prisma 追踪迁移)
在 Supabase 上手动迁移:
- 转到 SQL 编辑器
- 复制迁移文件内容来自
apps/api/prisma/migrations/<timestamp>-<name>/migration.sql - 在编辑器中运行
- 验证架构已更新
端点: GET /health(无需认证)
响应:
{
"data": {
"status": "ok",
"database": "connected"
},
"meta": { "requestId": "..." },
"error": null
}使用者:
- Kubernetes 活跃探针:
http://pod:3000/health - 负载均衡器健康检查
- 监控告警
结构化日志(Pino):
- 所有日志输出为 JSON(用 ELK、Datadog、CloudWatch 解析)
- 每请求有 requestId 用于追踪
- 日志级别可通过
LOG_LEVEL环境变量配置
示例日志条目:
{
"level": 20,
"time": "2026-04-27T00:00:00.000Z",
"pid": 1234,
"hostname": "pod-xyz",
"req": { "method": "GET", "url": "/hello" },
"res": { "statusCode": 200 },
"duration": 5,
"requestId": "uuid-v4",
"msg": "request completed"
}设置日志聚合:
- Datadog Agent → 日志到 Datadog
- AWS CloudWatch agent → 日志到 CloudWatch
- ELK Stack → 日志到 Elasticsearch
构建签名 APK(生产):
cd apps/mobile
fvm flutter build apk --release --dart-define=FLAVOR=prod
# 输出: build/app/outputs/apk/release/app-release.apk构建应用包(Play Store,推荐):
fvm flutter build appbundle --release --dart-define=FLAVOR=prod
# 输出: build/app/outputs/bundle/release/app-release.aab签名:
- 创建 keystore(一次性):
keytool -genkey -v -keystore ~/my-release-key.jks \ -keyalg RSA -keysize 2048 -validity 10000 \ -alias my-key-alias - 在
apps/mobile/android/key.properties中引用:storeFile=/path/to/my-release-key.jks storePassword=**** keyPassword=**** keyAlias=my-key-alias - 构建将自动签名
上传到 Play Store:
- 使用 Play Console(手动)
- 或 fastlane:
fastlane supply --aab=path/to/app-release.aab --package_name=com.yourcompany.app
构建签名 IPA(生产):
cd apps/mobile
fvm flutter build ipa --release --dart-define=FLAVOR=prod
# 输出: build/ios/ipa/mobile_boilerplate.ipa签名:
- 在 Apple 开发者中创建或更新配置文件
- 更新
ios/Runner.xcconfig:DEVELOPMENT_TEAM = ABC123XYZ CODE_SIGN_IDENTITY = iPhone Distribution - 构建将自动签名
上传到 TestFlight/App Store:
- 使用 Xcode Organizer(手动)
- 或 fastlane:
fastlane deliver --ipa=path/to/mobile_boilerplate.ipa
版本在 pubspec.yaml 中:
version: 0.1.0+1 # 0.1.0 = 语义版本,1 = 构建号更新流程:
- 编辑
pubspec.yaml中的version - 提交 + 推送到
main分支 - semantic-release 自动标记和发布
- CI 以新版本构建 APK/IPA
配置在项目根 .releaserc.cjs 中:
-
常规提交解析:
feat:→ 次版本碰撞fix:→ 补丁版本碰撞BREAKING CHANGE:→ 主版本碰撞
-
自动生成更新日志(附加到
docs/project-changelog.md) -
创建 Git 标签: 例如
v1.2.0或v1.2.0-beta.1 -
发布版本:
- Main 分支 → 生产发布(v1.2.0)
- Beta 分支 → 预发布(v1.2.0-beta.1)
-
制品:
- Git 标签推送
- GitHub 上的发布说明
- Docker 镜像标记 + 推送(如果 api 改变)
- 移动应用上传(如果移动改变,手动步骤)
semantic-release 仅在以下情况运行:
api-ci工作流通过(后端测试、lint、构建)mobile-ci工作流通过(前端测试、lint、构建)- 分支为
main或beta
示例: 若推送到 main 的功能破坏测试,semantic-release 会阻止发布直到修复。
| 分支 | 发布类型 | 示例标签 |
|---|---|---|
main |
生产 | v1.2.0 |
beta |
预发布 | v1.2.0-beta.1 |
feature/* |
无(仅 CI) | — |
develop |
无(仅 CI) | — |
- 后端:
DATABASE_URL=postgresql://localhost/boilerplate或SKIP_DB=true - 移动:
API_BASE_URL=auto、FLAVOR=dev
- 后端: Supabase 暂存项目、
LOG_LEVEL=debug - 移动:
API_BASE_URL=https://staging-api.yourcompany.com、FLAVOR=staging
- 后端: Supabase 生产、
LOG_LEVEL=info、监控启用 - 移动:
API_BASE_URL=https://api.yourcompany.com、FLAVOR=prod
移动配置为构建时(flutter build --dart-define=FLAVOR=prod)。
后端配置为运行时(部署时环境变量)。
后端(基于 Docker):
- 保留旧镜像标记:
mobile-boilerplate-api:v1.1.0(旧)、v1.2.0(新) - 若生产中断,重新部署旧镜像:
docker run -e DATABASE_URL=... mobile-boilerplate-api:v1.1.0
- 或使用 git:
git checkout v1.1.0 && docker build ...
移动:
- Play Store:使用"管理发布"→ 以前版本作为活跃
- App Store:在 App Store Connect 中使用 TestFlight 或以前版本
数据库(Prisma):
- 迁移在 git 中版本化
- 要回滚架构:
git revert <migration-commit>,然后prisma migrate deploy - 谨慎: 可能数据丢失;重大迁移前备份
设置 ENABLE_SWAGGER=true 在 NODE_ENV=development 之外宽松
Content Security Policy:script-src 允许 'unsafe-inline' 使 Swagger
UI 能渲染其内联脚本。
权衡: XSS 保护被大幅削弱。任何用户可控 内容在响应中反射时变为潜在可执行。
规则:
- ✅ 使用
ENABLE_SWAGGER=true用于短期、监管的调试会话在暂存 - ❌ 绝不在生产环境中开启
- ❌ 绝不在长期暂存环境中设置(暴露给实际用户)
若需要 Swagger 访问非开发 API 用于日常工作,使用
独立 Swagger UI 客户端(浏览器扩展、Postman、Swagger Editor)
指向 /api-docs/json — 保持服务器端 CSP 严格。
若 API 运行在负载均衡器、CDN 或反向代理后(Cloudflare、ALB、 Render、Fly.io、Nginx 等),设置:
TRUST_PROXY=1 # 1 跳 — 最常见
TRUST_PROXY=2 # CDN → LB → 应用 — 设置为实际跳数这使 req.ip 从 X-Forwarded-For 而非代理 IP 解析为真实客户端 IP。关键用于:
- 速率限制(Throttler 按 IP 键值请求)
- 审计日志
- 安全分析
陷阱: 绝不设置 TRUST_PROXY 高于真实跳数。
更高值 = X-Forwarded-For 变可伪造,攻击者可伪造任意 IP。
BODY_LIMIT=1mb 覆盖 JSON/urlencoded 体。用于文件上传:
- 勿提升
BODY_LIMIT吸收文件(影响所有路由)。 - 使用
multer单个路由带明确限制:FileInterceptor('file', { limits: { fileSize: 10 * 1024 * 1024 } })。
设置告警:
- HTTP 5xx 错误率 > 1%
- 请求延迟 p95 > 500ms
- 数据库连接池耗尽
- 磁盘使用 > 80%
- 内存使用 > 85%
工具:
- Datadog、New Relic、Prometheus、CloudWatch
- 在监控仪表板中设置阈值
- 通过 Slack、PagerDuty 告警
最后更新: 2026 年 4 月