Skip to content

七、生产环境部署 (Deployment)

本章提供完整的从零开始部署 TodoApp 后端服务的操作手册,适用于 Ubuntu 24.04 VPS 环境。


7.1 服务器要求

项目最低要求推荐配置
操作系统Ubuntu 22.04 LTSUbuntu 24.04 LTS
CPU1 核1 核
内存512 MB1 GB
磁盘10 GB20 GB
网络IPv4 公网地址IPv4 + IPv6 双栈
域名必须(申请 SSL 证书需要)已解析的二级域名

7.2 目录结构

~/todo_backend/
├── bin/
│   └── server.dart          ← 后端入口
├── lib/                     ← 后端源码
├── Dockerfile
├── docker-compose.yml
├── pubspec.yaml
├── .env                     ← 敏感配置(不进仓库)
├── .env.example             ← 配置模板(进仓库)
└── logs/                    ← 日志文件目录(不进仓库)
    └── server_YYYY-MM-DD.log

7.3 环境变量配置清单

~/todo_backend/ 目录下创建 .env 文件,参考以下模板填写:

env
# ── 服务器 ────────────────────────────────────────────────
PORT=8080
HOST=::

# ── JWT 认证 ──────────────────────────────────────────────
JWT_SECRET=<用 openssl rand -hex 32 生成>
ACCESS_TOKEN_EXPIRE_HOURS=1
REFRESH_TOKEN_EXPIRE_DAYS=30

# ── PostgreSQL ────────────────────────────────────────────
DB_NAME=todo_db
DB_USER=todo_user
DB_PASSWORD=<强密码>

# ── 邮件服务(Resend)────────────────────────────────────
RESEND_API_KEY=<your-resend-api-key>

# ── 日志查看接口认证 ──────────────────────────────────────
LOG_USER=<管理员用户名>
LOG_PASS=<强密码>
LOG_SECRET=<用 openssl rand -hex 32 生成>

生成随机密钥:

bash
openssl rand -hex 32

7.4 安装依赖环境

安装 Docker 和 Docker Compose

bash
# 更新包索引
sudo apt update

# 安装 Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker

# 验证安装
docker --version
docker compose version

安装 Nginx

bash
sudo apt install -y nginx
sudo systemctl enable nginx
sudo systemctl start nginx

安装 Certbot(Let's Encrypt 证书)

bash
sudo apt install -y certbot python3-certbot-nginx

7.5 Docker Compose 部署

克隆代码

bash
git clone <your-repo-url> ~/todo_backend
cd ~/todo_backend

创建日志目录

bash
mkdir -p ~/todo_backend/logs

构建并启动服务

bash
docker compose up -d --build

查看运行状态

bash
# 查看容器状态
docker compose ps

# 实时查看后端日志
docker compose logs -f api

# 查看数据库日志
docker compose logs -f db

正常启动后应看到:

todo_api  | 2026-05-20 10:00:00.123 [INFO ] [Logger] ServerLogger 初始化完成
todo_api  | 2026-05-20 10:00:00.456 [INFO ] [DB   ] 数据库初始化完成
todo_api  | 2026-05-20 10:00:00.789 [INFO ] [Server] 启动成功,监听 ::8080

7.6 Nginx 配置

创建配置文件

bash
sudo vim /etc/nginx/sites-available/todo-api

写入以下内容(将 api.todo.wangpudev.com 替换为你的域名):

nginx
# HTTP → HTTPS 重定向
server {
    listen 80;
    listen [::]:80;
    server_name api.todo.wangpudev.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

# HTTPS 主配置
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name api.todo.wangpudev.com;

    ssl_certificate     /etc/letsencrypt/live/api.todo.wangpudev.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.todo.wangpudev.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

    add_header Strict-Transport-Security "max-age=63072000" always;
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;

    # 普通 API 请求
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        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_connect_timeout 60s;
        proxy_send_timeout    60s;
        proxy_read_timeout    60s;
    }

    # SSE 长连接(关闭缓冲,保持长连接)
    location /events/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        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_set_header Connection '';

        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
        gzip off;
    }

    # 日志查看接口
    location /logs/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        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;
    }
}

启用配置

bash
sudo ln -s /etc/nginx/sites-available/todo-api /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

7.7 配置 Resend 邮件服务

TodoApp 的密码重置功能通过 Resend 发送验证码邮件。Resend 是一个面向开发者的邮件发送服务,免费套餐每月可发送 3000 封邮件,对个人项目完全够用。

注册并获取 API Key

  1. 访问 resend.com 注册账号
  2. 进入 Dashboard → API KeysCreate API Key
  3. 权限选择 Sending access,复制生成的 Key(格式为 re_xxxxxxxxxx
  4. 将 Key 填入 .env
env
RESEND_API_KEY=re_xxxxxxxxxx

验证发件域名

免费套餐可以直接用 Resend 提供的测试域名发送,但建议绑定自己的域名,邮件送达率更高且更专业。

第一步:进入 Dashboard → DomainsAdd Domain,输入你的域名(如 todo.wangpudev.com)。

第二步:Resend 会提供几条 DNS 记录,在你的 DNS 控制台(Cloudflare 等)添加:

类型名称
TXTresend._domainkey<Resend 提供的 DKIM 值>
MXsendfeedback-smtp.us-east-1.amazonses.com
TXTsendv=spf1 include:amazonses.com ~all

第三步:等待 DNS 生效(通常几分钟),Resend 控制台显示 Verified 后即可使用。

配置发件地址

本项目的发件地址在后端代码中配置,默认为 todo@todo.wangpudev.com。如需修改,编辑 lib/utils/email_util.dart

dart
static Future<void> sendVerificationCode({
  required String to,
  required String code,
}) async {
  await _send(
    from: 'todo@todo.wangpudev.com',  // ← 修改为你的发件地址
    to: to,
    subject: '【待办】密码重置验证码',
    html: '''
      <p>你的密码重置验证码为:</p>
      <h2 style="letter-spacing:4px;">$code</h2>
      <p>验证码 10 分钟内有效,请勿泄露给他人。</p>
    ''',
  );
}

注意:发件地址的域名必须与 Resend 中已验证的域名一致,否则邮件会被拒绝发送。

验证邮件功能

部署完成后,在应用的「忘记密码」页面输入你的注册邮箱,点击发送验证码,确认能正常收到邮件。

如果收不到,检查以下几点:

bash
# 查看后端日志中的邮件发送记录
docker compose logs api | grep -i email
  • [Email] 邮件发送失败:检查 RESEND_API_KEY 是否正确填入 .env 并重启容器
  • 域名未验证:Resend 控制台确认域名状态为 Verified
  • 邮件进了垃圾箱:域名 DNS 记录未正确配置 SPF / DKIM

7.8 申请 SSL 证书

确保域名已解析到 VPS 的公网 IP,然后执行:

bash
sudo certbot --nginx -d api.todo.wangpudev.com

按提示操作,选择强制重定向 HTTP 到 HTTPS。证书申请成功后 Certbot 会自动修改 Nginx 配置。

验证自动续期:

bash
sudo certbot renew --dry-run

7.9 客户端构建

Windows 桌面端

powershell
flutter build windows --release

产物位于 build\windows\x64\runner\Release\,将整个 Release 文件夹打包分发即可。

Android APK

针对 arm64 架构单独打包,体积更小(约为全架构包的 1/3):

powershell
flutter build apk --release --target-platform android-arm64

产物位于 build\app\outputs\flutter-apk\app-arm64-v8a-release.apk

注意:Release 包需要在 AndroidManifest.xml 中声明 INTERNET 权限,否则网络请求全部失败。Debug 模式会自动注入此权限,Release 不会。


7.10 日常运维命令速查

后端服务管理

bash
# 查看所有容器状态
docker compose ps

# 重启后端服务
docker compose restart api

# 更新代码后重新构建并启动
docker compose down
docker compose build
docker compose up -d

# 查看实时日志(Ctrl+C 退出)
docker compose logs -f api

# 查看最近 100 行日志
docker compose logs --tail=100 api

数据库管理

bash
# 进入数据库容器
docker exec -it todo_db psql -U todo_user -d todo_db

# 查看所有表
\dt

# 退出
\q

日志管理

bash
# 查看今天的日志文件
ls ~/todo_backend/logs/

# 实时监控日志文件
tail -f ~/todo_backend/logs/server_$(date +%Y-%m-%d).log

# 搜索 ERROR 级别日志
grep "ERROR" ~/todo_backend/logs/server_$(date +%Y-%m-%d).log

# 通过接口查看日志(需要替换 TOKEN)
curl -u 'LOG_USER:LOG_PASS' \
  'https://api.todo.wangpudev.com/logs/?level=error&limit=50'

Nginx 管理

bash
# 检查配置语法
sudo nginx -t

# 重载配置(不中断服务)
sudo systemctl reload nginx

# 查看 Nginx 错误日志
sudo tail -f /var/log/nginx/error.log

SSL 证书管理

bash
# 查看证书有效期
sudo certbot certificates

# 手动续期
sudo certbot renew